Cover Page

Copyright © 2002 by Microsoft Corporation


LOC

PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399

Copyright © 2002 by Brad Leupen

All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher.

Library of Congress Cataloging-in-Publication Data
Wyke, R. Allen.
XML Programming (Core Reference) / R. Allen Wyke, Sultan Rehman, Brad Leupen.
p. cm.
Includes index.
ISBN 0-7356-1185-8
1. XML (Document markup language) 2. Computer programming. I. Rehman, Sultan.
II. Leupen, Bradley. III. Title.

QA76.76.H94 W78 2001
005.7'2--dc21 2001135476

Printed and bound in the United States of America.

1 2 3 4 5 6 7 8 9 QWT 7 6 5 4 3 2

Distributed in Canada by Penguin Books Canada Limited.

A CIP catalogue record for this book is available from the British Library.

Microsoft Press books are available through booksellers and distributors worldwide. For further information about international editions, contact your local Microsoft Corporation office or contact Microsoft Press International directly at fax (425) 936-7329. Visit our Web site at www.microsoft.com/mspress. Send comments to mspinput@microsoft.com.

BizTalk, Developer Studio, Microsoft, Microsoft Press, Mobile Explorer, MS-DOS, MSDN, MSN, Natural, Outlook, Visio, Visual Basic, Visual SourceSafe, Visual Studio, Windows, and Windows NT are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. Other product and company names mentioned herein may be the trademarks of their respective owners.

The example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred.

Acquisitions Editor: David Clark
Project Editor: Barbara Moreland

Body Part No. X08-05017


Dedication

This book is definitely for my girl—J. She has helped me through some very tough times and situations, and through it all maintained the love and wonderful smile that makes every day worth living.

—R. Allen Wyke

This book is dedicated to my friends, family, and loved ones. C., I could not have done it without your support and encouragement.

—Brad Leupen

To Meeasap—I wish you could have seen this.

—Sultan Rehman


Acknowledgments

R. Allen Wyke

On the publishing side I would like to thank Bob Kern of TIPS Publishing and my coauthors, Sultan and Brad, for their professionalism, hard work, and overall support in proposing and the writing of this book. I would also like to thank all the people at Microsoft Press who worked on the book and helped make sure it was the best it could be.

Finally I would like to thank Phil and Donald for our weekly ATV trips. Often battered and bruised, these rides reduced more stress than a person could imagine.

Brad Leupen

I would also like to thank Bob Kern of TIPS Publishing and my coauthors for their hard work and dedication, including Alan Houser for his excellent work. Writing a book of this scope is a team effort. Their help has been invaluable. I would also like to thank the folks at Microsoft Press for making this project a reality.

Last, but not least, I would like to thank Chris Reeves for his help in building the example application. His hard work and perseverance are much appreciated.

Sultan Rehman

I would like to thank the many people who have helped to bring this book to fruition. I especially owe a great debt of gratitude to my coauthor, colleague and friend, Allen Wyke. Without your support and comments my chapters would have needed a considerable amount of editing.

Thanks to my wife, Candy, for your unending love and for streamlining my life so that I could concentrate on my work.

Last, but definitely not least, I would like to thank my brother, Sam, for your companionship and for showing me that struggling is a gift in life.


An Architectural Overview of XML

When first exposed to the Extensible Markup Language (XML), you might ask yourself how it fits in the overall picture of Web-related items. This is the same question developers, managers, and presidents ask, not only of their peers, but of themselves as well. Ironically, this is the same question people in those roles asked themselves years ago with other computer-based equipment such as personal computers, databases, and even Microsoft Windows.

In determining how a particular technology fills a void and solves a problem, one must first know what the problem is and why it is present. Without this basic information, trying to create the right solution is a gamble. We've all seen, and most likely worked for, companies that build a product and service just because "it's cool" or "great" without knowing and understanding the problem it was supposed to solve.

In this chapter, we will go over how XML fits into the big picture and how it can help solve problems you might be facing. We touch on how XML is able to accomplish these tasks and point you in the right direction for the next steps. This chapter will also describe the landscape for the rest of the book so that the information and examples that follow are understandable, not only from a technical perspective, but from a problem-solving one as well. Exactly how these developments affect software development will be examined. We'll also discuss how a new programming model is required to address the needs of today's computer users and how Microsoft answers those needs with the Microsoft .NET Framework.


How Computing Started and Has Evolved

The world of computing was dominated by the mainframe just over 20 years ago. Few people had access to computers, and then only via the nearest IT department. The Personal Computer (PC) and the Graphical User Interface (GUI) changed that, opening the doors to computing for tens of millions of people and transforming the computer into a mass-market product. Corporations realized that networks of PCs and PC-based servers could change the way they did business.

When the Internet arrived, it revolutionized communications, created a rich source of information and entertainment, and added an "e" to business. Now close to 300 million people use the World Wide Web. According to International Data Corporation, more than a quarter of a trillion dollars in business will be transacted over the Internet this year alone.

System Design Issues

Computers and the Internet developed in parallel for some time and in some sense drive each other. However, they did not previously support each other as much as they could have. For example, some Internet and Web technologies—initially developed in the late 1960s and early 1970s and enhanced and refined in the early 1990s—are constrained by design to the least common denominator of computing devices connected to the Internet. They do not even take advantage of the enormous capabilities of today's least expensive PCs.

At the same time, most application software designed for PCs does not yet fully exploit the capabilities of internal corporate networks, let alone the power of a global network capable of supplying electronic applications on demand. The client/server development model that dominated the past decade tried to harness some of this power.

Client/Server Development

Client/server development breaks an application down into two or more layers, usually these three: the presentation layer, which encapsulates interacting with the user; the application layer, which encapsulates business rules and object persistence; and the data layer, which encapsulates access to database management systems. Objects in each of these layers perform small, discreet functions.

Because an application could be broken into multiple components, developers gained the ability to distribute components between multiple PCs within a corporate network, thereby harnessing some of the lost processing power by using a single machine for each component. To distribute these objects, however, they had to contain information on how and where another object could be found. The objects, therefore, became tightly coupled.

This coupling required a homogeneous computing environment so the system could run at peak efficiency. While corporations were able to control their own computing environment (at least to some extent), as soon as these applications crossed a firewall to the Internet, the computing environment changed drastically from one Web site to the next.

Needs of These Systems

Today's Internet combines the old mainframe model and the current client/server model. Despite the availability of bandwidth, information is still locked up in centralized databases, with gatekeepers (Web servers) controlling access. Users rely on these servers to perform most operations, just like the mainframe timesharing model. The Web server, in turn, invokes objects on other servers to perform several tasks simultaneously. Still, even with this interaction between Web servers, application servers, and database servers, Web sites are isolated islands of information and functionality.

In this environment the user must adapt to the technology instead of vice versa. Corporate administrators and planners face additional challenges. While the introduction of server farms has made the overall computing experience more reliable, it has also made system management more complex. Performance measurement, capacity planning, and operations management are more challenging in today's multitier, multifunction Web sites.

What we need is a model for developing software that will allow developers to exploit both the power of the PC and the global network, which will make truly distributed computing possible. Furthermore, this framework must facilitate the separation of data from the way in which the data is presented—it must be a true peer-to-peer network that will allow information to flow freely between devices.


Introducing XML

The Web gives you unlimited amounts of information—or does it? You can buy a ticket for a flight from New York to Miami over the Internet, but can you search for the cheapest fare? Can you price a different route when disconnected from the Web? Can you load the fare into your expense report? The answers is always no. The Web and the Hypertext Markup Language (HTML) give you access to information, but they do not enable you to leverage it.

For example, say you want to purchase a ticket. You go to http://www.myfavoriteairlines.com and download the Extensible Hypertext Markup Language (XHTML) page, mfa-index.html, shown in Listing 1-1.

Listing 1-1 mfa-index.html: An airline ticket purchase page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
  <head>
    <title>Welcome to My Favorite Airlines</title>
  </head>
  <body>
    <h1>Connecting flights to Miami</h1>
    <p>
      Buy Direct from us and Save $$$! New York - Orlando - Miami..$95
    </p>
    <h1> Direct flights to Miami</h1>
    <p>
      Buy Direct from us and Save $$$!  New York - Miami..$195
    </p>
    <h1> Your Selection</h1>
    <p>
      Your flight with tax and headphones: $210
    </p>
 </body>
</html>

This page, shown in Figure 1-1, displays information about flights to Miami, but you can't use it for much more. If you wanted to search for the fare itself, a simple search engine might look for a dollar sign, but in this example you would erroneously get a hit on "Save $$$." If you wanted to list only direct flights, a filter could try to interpret the text by looking for the word "Direct." Screen-scraping algorithms, however, are limited by how cleverly they can interpret the author's use of language. So, XML to the rescue!

Figure 1-1 Viewing our simple XHTML document in a browser.

XML Compared to XTML

XML is a Recommendation from the World Wide Web Consortium, commonly referred to as the W3C ( http://www.w3.org ), the multicompany group that defined XHTML and its predecessor, HTML. XML is a vehicle for information that brings usable data to the desktop and is a universal data format that does for data what HTML does for Web content—it provides the necessary markup. Because the source code of languages defined in XML looks like HTML, it's useful to compare the two.

XML consists of hierarchically nested fields like HTML, it is just as easy to read, and it is portable. However, where HTML contains titles, headings, and italics, XML can contain customers, order numbers, prices, or any data element you need. XML is fully extensible so you can add new tags and new elements to support your application.

The Core of XML

Structured information contains both content (words, pictures, and so forth) and a suggestion of what function that content plays. For example, content in a section heading has a different meaning from content in a footnote, which is different from content in a Figure caption or a database table. The XML specification defines a standard way to structure the markup of documents.

Why XML?

Programmers created XML so that richly structured documents could be used over the Web. The only viable alternatives, HTML and the Standard Generalized Markup Language (SGML), are not practical for this purpose. HTML comes bound with a set of semantics and does not provide arbitrary structure. SGML provides arbitrary structure, but implementing SGML is too difficult for a Web browser to do on its own. XML specifies neither semantics nor a tag set. It is a metalanguage for describing markup languages and provides a facility for defining tags and the structural relationships between them. Because there's no predefined tag set, there aren't any preconceived semantics. The semantics of an XML document will either be defined by the applications that process them or by style sheets.

XML Documents

If you use HTML or SGML, XML documents will look familiar. Let's revisit the airline example. If the Web page included XML data as in Listing 1-2, the information in mfa-sample.xml could be sent with the page.

Listing 1-2 mfa-sample.xml: An XML version of our airline ticket information.

<?xml version="1.0"?>
<flightdata>
   <ny_mia_flights>
      <direct>
        <cost>195</cost>
      </direct>
      <connecting>
        <layover duration="90" durationtype="minutes">Orlando</layover>
        <cost>95</cost>
      </connecting>
    </ny_mia_flights>
</flightdata>

The document, which is displayed in Figure 1-2, begins with an XML declaration: <?xml ...?>. While not required, its presence unequivocally identifies the document as an XML document and indicates the version of XML to which it was authored.

Figure 1-2 Loading our sample XML document in Microsoft Internet Explorer.

Elements are the most common form of markup. Delimited by angle brackets, most elements identify the nature of the content they surround. Some elements might be empty, in which case they have no content. If an element is not empty it begins with a start-tag, <element> and ends with an end-tag, </element>.

Attributes are name-value pairs that occur inside start-tags after the element name. For example, <layover duration="90" durationtype="minutes"> is a <layover> element where the attributes duration and duration type have the values "90" and "minutes". All attribute values must be quoted. Either single or double quotes can be used in pairs.

If the HTML page described earlier included this data, you could easily identify the price of a flight because it is delimited with <cost> tags. Identifying which flights are direct is also simple because the <direct> element is nested within <ny_mia_flights>.

Usable data is shipped with the Web page, so you can calculate how much more a direct flight would have cost while you're stranded on the runway at Orlando on your connecting flight in the middle of a hurricane. XML is great for customers and it makes Web sites easier to build and maintain. If My Favorite Airlines used XML for its data-driven Web site, the company could use the same applet to calculate the total fare on every page. And should the tax rate change, when using appropriately structured XML you need only update the database, not every HTML page. XML is also extensible, so My Favorite Airlines can add a new element for meal preference (for example, <meal>Vegetarian</meal>) without disrupting the rest of the site.

Document Type Declarations

For any given application, however, elements occurring in a completely arbitrary order are meaningless. Consider the flight data example in Listing 1-3. Would the contents of the following mfa-bad.xml be meaningful?

Listing 1-3 mfa-bad.xml: A bad example of using XML.

<flightdata>
  <meal>
    <layover>
      orlando
      vegetarian
    </layover>
  </meal>
  <cost>
    <direct>
      195
    </direct>
  </cost>
</flightdata>

This example document is so far outside the bounds of what we expect that it's absurd. It doesn't mean anything, as you can see in Figure 1-3. From a strictly syntactic point of view, however, there's nothing wrong with this document. Therefore, if the document is to have meaning, and certainly if you need an application to process it, there must be some constraint on the sequence and nesting of tags. These constraints can be expressed in a Document Type Definition (DTD) or the newer XML Schemas (XSD).

Figure 1-3 Meaninglessly defining our content in a semi-XML format.
XML 1.0 and XML Schema

The W3C released the XML 1.0 Recommendation in February 1998. The full text can be accessed at http://www.w3.org/TR/1998/REC-xml-19980210. The WC3 issued an update, with minor corrections, in October 2000, which is located at http://www.w3.org/TR/2000/REC-xml-20001006.

The XML Schema specification reached full W3C Recommendation status in May 2001. It has two normative parts. Part 1 (Structures) is at http://www.w3.org/TR/xmlschema-1/ . Part 2 (Datatypes) is at http://www.w3.org/TR/xmlschema-2/ . In addition to the two normative documents, the W3C has provided a useful non-normative Primer to XML Schema, Part 0, at http://www.w3.org/TR/xmlschema-0/ .

Generally, DTDs and XML Schemas allow a document to communicate metadata to the parser about its content. Meta-information includes the allowed sequence and nesting of tags, attribute values and their types and defaults, the names of external files that might be referenced and whether or not they contain XML, the formats of some external (non-XML) data that might be referenced, and the entities that might be encountered.

Well-Formed and Valid Documents

The are two categories of XML documents. A document is either "well-formed" or "valid". A document can be well-formed only if it obeys the syntax of XML. A document that includes sequences of markup characters that cannot be parsed or are invalid cannot be well-formed. In addition, the document must meet all of the following conditions:

  • There can be one, and only one, root element.
  • All tags that are opened must be closed.
  • Tag names are case-sensitive.

Additional, less critical rules include

  • No attribute can appear more than once on the same start-tag.
  • Non-empty tags must be properly nested.
  • Parameter entities must be declared before they are used. If a document is not well-formed, it is not XML. This means that all XML documents are well-formed, and XML processors are not required to do anything with documents that are not.

A well-formed document is valid only if it contains or refers to a proper DTD or XML Schema and if the XML document obeys the constraints of that DTD or XML Schema (that element sequence and nesting is valid, required attributes are provided, attribute values are of the correct type, and so forth). We will talk more about validity in Chapter 2.


How Is XML Relevant?

Although the idea of using "metadata" to describe data only recently came to the fore, the concept has been around for a long time. Databases, for instance, are made up of tables, columns, views, and such, which are nothing more than metadata about the actual data contained in other tables. These items help describe the data without knowing what is contained in it. However, simply using names, perhaps combined with data types, does not solve all the problems present when trying to describe and ultimately understand data.

XML becomes the glue that binds what a piece of data actually is to what it is supposed to accomplish. It's used to describe all aspects of the data, ranging from near-physical properties to usage instructions, and its relationship to other data. This information can be used for human and machine-readable purposes, one of the true advantages of XML.

What Problem Does XML Solve?

Before the release and widespread adoption of XML, there were relatively few methods that could solve the problem of describing data in a standardized method so that anyone, including machines, could understand what the data meant. Work, on SGML and other electronic data such as the Electronic Data Interchange (EDI) for example, was done in the publishing world that demonstrated the potential of standardized systems, descriptions, and methods. However, SGML and EDI were expensive, either because of the software needed to handle the data or the training necessary to teach these complex markup languages.

Research helped create XML (a subset of SGML), which is able to describe data in a standardized way but is easier to understand and implement. Another reason this is useful is because today most everyone uses computers, so even the "nontechnical" people might need to be able to describe the data they have so others can understand it. A simple example is meeting.xml, shown in Listing 1-4.

Listing 1-4 meeting.xml: A quick XML example that can be used for nontechnical purposes.

<?xml version="1.0"?>
<meeting date="2001-07-29" time="14:30:00">
  <actionItem who="fred">Send out minutes after meeting</actionItem>
  <actionItem who="sally">Set up presentation for Thursday</actionItem>
  <actionItem who="dan">Present product roadmap</actionItem>
  <actionItem who="tom">Send out agenda for next meeting</actionItem>
</meeting>

Even though we didn't describe what this code listing is, do you have any doubt as to what it represents? It represents action items from a meeting on July 29, 2001 at 14:30:00 (2:30 P.M.).

Another example is map.xml, shown in Listing 1-5, which defines directions from one location to another. These examples demonstrate how you can use XML to describe data that can be understood by a human, machine, or software.

Listing 1-5 map.xml: Using XML to describe directions to a particular location.

<?xml version="1.0"?>
<map>
  <start>
    <addr1>100 West Morgan St</addr1>
    <city>Raleigh</city>
    <state>NC</state>
    <zip>27603</zip>
  </start>
  <directions>
    <left distance="0.11 miles">W MORGAN ST</left>
    <left distance="0.11 miles">S WILMINGTON ST</left>
    <left distance="0.44 miles">E EDENTON ST</left>
    <right distance="0.09 miles">N WEST ST</right>
    <left distance="0.02 miles">W JONES ST</left>
  </directions>
  <destination>
    <addr1>508 W Jones St</addr1>
    <city>Raleigh</city>
    <state>NC</state>
    <zip>27603</zip>
  </destination>
</map>

We specifically used these nontechnical examples to show that XML can solve many of the difficulties of exchanging or communicating data between two parties, human or machine. Although XML is often considered good only for moving data from A to B (in a manner where B understands what the data is), it can also be used for a variety of tasks, as the following examples demonstrate.

Why Is XML a Good Choice?

If you asked a few years ago whether XML was a good choice for defining your data, you would have received mixed responses. While most people would have agreed that XML would soon evolve as the de facto standard, they would have hedged their bets by saying that it might be wise to wait to ensure wide spread adoption. The flexibility and extensibility of using it as a means to describe data, on the other hand, positioned it for certain success. Today, when Microsoft Windows XP, Microsoft Office XP, and the .NET line of server products are on the shelves, XML is likely a requirement.

Let's take a simple example and demonstrate XML's flexibility by using it to define the dimensions of a door. We can use it not only to describe the data, but also to apply an Extensible Stylesheet Language Transformation (XSLT) to transform it into XHTML.

If we load the example in Listing 1-6 in an XML-supporting version of Internet Explorer, we should see something like Figure 1-4. This is a display of the text, with a default style sheet applied, after it is verified as well-formed. If something is mistyped, like the name of an element, an error will be generated.

Listing 1-6 door.xml: A document describing the dimensions of a wooden door.

<?xml version="1.0"?>
<door>
  <height type="inches">80</height>
  <width type="inches">36</width>
  <material type="wood"/>
</door>
Figure 1-4 Loading a simple XML document into Internet Explorer 5.

We could send this information to a supplier to see if they had any wooden doors that measure 36 by 80 inches, but that is not the limit of what we can do with this data. Suppose we wanted to display this information on a Web site, which would require us to format the information in HTML/XHTML. We can do this by including the following line in the document:

<?xml-stylesheet type="text/xsl" href="transform.xsl"?>

Adding this line, by itself, does not perform the necessary transformation from XML into XHTML; it only tells the parser that a corresponding style sheet for the document exists. We must now create a transform.xsl document to define the transformation, which would look like Listing 1-7.

Listing 1-7 transform.xsl: An XSLT style sheet responsible for transforming our XML document into XHTML.

<?xml version="1.0"?>
<xsl:stylesheet
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
      xml:lang="en">
<head>
  <title>Door Information</title>
</head>
<body>
  <h1>Door Information</h1>
  <p>
    <strong>
      Material:
    </strong>
    <xsl:value-of select="//door/material/@type"/>
    <br/>
    <strong>
      Height:
    </strong>
    <xsl:value-of select="//door/height"/>
    (measured in <xsl:value-of select="//door/height/@type"/>)
    <br/>
    <strong>
      Width:
    </strong>
    <xsl:value-of select="//door/width"/>
    (measured in <xsl:value-of select="//door/width/@type"/>)
  </p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The Microsoft XML 3 parser (MSXML) introduced XSLT support into Microsoft's XML-supporting line of products. This parser, which was first available for Internet Explorer 4.01 Service Pack 1 as a separate download, had limited XSLT support. If you want to see this example work in Internet Explorer 5 or greater, you will need MSXML 4, which you can download from http://msdn.microsoft.com .

If we load door.xml in Internet Explorer the browser will apply the style sheet, transforming it from XML into XHTML, and you will see something like Figure 1-5.

This example illustrates why XML is a good choice for describing data. Data can be exchanged with partners, affiliates, and internal applications, and it can be used to describe your own internal content. With a little imagination you can see just how useful XML can be.

Figure 1-5 Transforming our door.xml document into XHTML.


What Can It Help?

We've heard how great XML is, how it helps with certain issues, and seen a few examples, but what does this mean? What types of difficulties does it solve, how does it solve them, and why is XML the solution for these issues? These are questions that we need to ask ourselves before choosing XML as our solution-providing method. We need to be sure it is the right means to provide integration points with our applications, or descriptions and structure to our content.

In this section we will talk about specific uses of XML and how they work. Because most of these concepts will be applied in greater detail later in the book, these example cases will not cover every detail of the code involved. What is important to understand after completing this section is that XML can be used in the creation of objects, application messaging, and process modeling.

Working with Objects

Those of you familiar with the principles and practices of object-oriented programming will have added respect, and probably motivation, for creating items in an object-oriented manner. Doing so allows for a degree of flexibility and potential component reuse that is not possible when operating in a non-object environment. XML is no different and has its own object-like implementations and uses. We will cover these in the following sections.

Modeling

Soon after XML 1.0 was released, gaps in the language were filled with complementary standards. One such limitation of XML was the inability to present data in an object-oriented manner. To help fill this void, a Note at the W3C was published titled "Schema for Object-Oriented XML (SOX)". This Note, which became an important part of the foundation that was used to build XSD, extended XML 1.0 by supporting the following six items. (Because we cover XSD in Appendix A, we use SOX in this example.)

  • An extensive and extensible set of datatypes
  • Inheritance among element types
  • Namespaces
  • Polymorphic content
  • Embedded documentation
  • Features that enable robust distributed schema management

SOX makes it possible, for example, to create a home computer object that contains a monitor, housing (CPU, RAM, hard disk, and so forth), speakers, a keyboard, and a mouse. The definition of this schema shown, in Listing 1-8, home_computer.sox, gives you the ability to create documents describing various home computers but does not allow you to describe another type of computer that might have more options.

Because SOX enforces a valid Uniform Resource Identifier (URI), we must include the file:// portion of the URL in the <schema> element. For this example we have placed the schema on the S: drive of a Windows machine, so please adjust this attribute value for your system.

Listing 1-8 home_computer.sox: A schema describing our home computer.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE schema SYSTEM
  "urn:x-commerceone:document:com:commerceone:xdk:xml:schema.dtd$1.0">
<schema uri = "file:///S:/home_computer.sox" soxlang-version = "V0.2.2">
    <elementtype name = "home_computer">
        <model>
            <sequence>
                <element type = "monitor"/>
                <element type = "housing"/>
                <element type = "speakers"/>
                <element type = "keyboard"/>
                <element type = "mouse"/>
            </sequence>
        </model>
    </elementtype>
    <elementtype name = "monitor">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "housing">
        <model>
            <sequence>
                <element type = "cpu"/>
                <element type = "ram"/>
                <element type = "disk_space"/>
                <element type = "modem"/>
            </sequence>
        </model>
    </elementtype>
    <elementtype name = "speakers">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "keyboard">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "mouse">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "cpu">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "ram">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "disk_space">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "modem">
        <model>
            <string/>
        </model>
    </elementtype>
</schema>

Figure 1-6 is a visual representation of this schema.

Figure 1-6 A visual representation of our home computer data model.

This data model is fine if we only define home computers, but what happens if we want to define a work computer? Using SOX we can build such an object by extending our base <home_computer> element as shown in Listing 1-9. If we link in our home_computer.sox schema, define a new element called <work_computer>, and say it extends <home_computer>, we are able to add items like a printer, scanner, and zip drive. This new schema, work_computer.sox, now inherits all the elements in the original schema and requires only that we define the additions.

Listing 1-9 work_computer.sox: A new schema extending our home_computer.sox schema.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE schema SYSTEM
  "urn:x-commerceone:document:com:commerceone:xdk:xml:schema.dtd$1.0">
<schema uri = "file:///S:/home_computer.sox" soxlang-version = "V0.2.2">
    <join system = "file:///S:/home_computer.sox"/>
    <elementtype name = "work_computer">
        <extends type = "home_computer">
            <append>
                <sequence>
                    <element type = "scanner"/>
                    <element type = "zip_drive"/>
                    <element type = "printer"/>
                </sequence>
            </append>
        </extends>
    </elementtype>
    <elementtype name = "scanner">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "zip_drive">
        <model>
            <string/>
        </model>
    </elementtype>
    <elementtype name = "printer">
        <model>
            <string/>
        </model>
    </elementtype>
</schema>

Figure 1-7 provides a representation of this new schema so you can compare it to the home_computer.sox schema.

Now that the work computer schema is defined, we can create an instance document of this schema. Because SOX-compliant parsers will pull in the necessary parent schemas, we only need to reference the work_computer.sox schema in our example shown in Listing 1-10.

Listing 1-10 first_work_computer.xml: An instance of our work_computer.sox schema

<?xml version = "1.0" encoding = "UTF-8"?>
<?soxtype file:///S:/work_computer.sox?>
<work_computer>
    <monitor>15 inch</monitor>
    <housing>
        <cpu>1 GHz</cpu>
        <ram>256 MB</ram>
        <disk_space>60 GB</disk_space>
        <modem>56k</modem>
    </housing>
    <speakers>JBL</speakers>
    <keyboard>Microsoft</keyboard>
    <mouse>Microsoft</mouse>
    <scanner>Microtek</scanner>
    <zip_drive>100 MB</zip_drive>
    <printer>HP</printer>
</work_computer>
Figure 1-7 A visual representation of our new schema base on our home computer schema.

Object modeling allows you to reuse components of XML schema dialects, like SOX or XSD, which can lead to increased productivity within your development environment by cutting down on redundant work.

Procedure Invocation

Invoking new derivative classes of XML objects is not the only way you can use XML in an object-oriented manner. The ability to invoke a procedure on a different system using XML is also possible with several standards, such as Simple Object Access Protocol (SOAP), which we cover in Chapter 14, and XML-Remote Procedure Call (XML-RPC). Both standards represent a way you can pass information to an object (an application in many cases), process the information, and return the results.

RPC allows you to tap another machine, application, or device for its functionality, creating a virtual application to perform a given task. RPC is not a new concept, so many of you might have some experience working with it. XML-RPC, however, is relatively new.

XML-RPC is a simple specification that uses a Hypertext Transfer Protocol (HTTP) POST request to pass a set of commands, parameters, and so forth to a remote object. This request, which is broken into a header and payload, is then processed by the remote application. A response is returned once complete, which could contain either the results of the processing or an appropriate error. Let's walk through an example to help describe the requirements of XML-RPC.

While the request is a standard HTTP POST, XML-RPC dictates that the following items must be contained in the head of the request. Note that the traditional first line of an HTTP request, which contains the method (POST), requested URI (such as /scripts/xml-rpc-processor.exe) and protocol version (such as HTTP/1.1), is implied in this list, so we will not list these requirements separately. A sample is included after the list.

  • User-Agent: user agent making the request
  • Host: hostname of the application making the request
  • Content-Type: should state text/xml to define that the request contains XML information
  • Content-Length: the size of the document being passed
User-Agent: SomeApp/1.1 (Windows XP)
Host: database.example.com
Content-Type: text/xml
Content-length: 361

The body of the request contains the elements necessary to identify the procedure as well as pass in any arguments. In rpc-request.xml shown in Listing 1-11, we will use an application called validateUser.exe and we will pass it a first name and age parameter. Although this is a fictitious application, it could represent an application that validated a user's access, by name and age, against certain information. Note that the server processing the XML-RPC request would need to know what validateUser.exe was or where it was located; it would need to be able to launch the application.

Listing 1-11 rpc-request.xml: An XML-RPC request document.

<?xml version="1.0"?>
<methodCall>
  <methodName>validateUser.exe</methodName>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>FirstName</name>
            <value>
              <string>John</string>
            </value>
          </member>
          <member>
            <name>Age</name>
            <value>
              <i4>28</i4>
            </value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodCall>
More information on these values and what they mean in HTTP terms can be found at http://www.w3.org/Protocols/rfc2616/rfc2616.txt .

The document begins with the <methodCall> element followed by the <methodName> and a list of <params>. For more information on the various types of information you can pass, visit http://www.xmlrpc.com/spec .

The response, like the request, will use the HTTP protocol. To be validated, at a minimum it must return the following information in the header:

  • Response Code: see HTTP specification for more information on the various response codes
  • Content-Length: the size of the document being passed
  • Content-Type: should state text/xml to define that the request contains XML information

An example would look like this:

HTTP/1.1 200 OK
Content-Length: 219
Content-Type: text/xml

As before, you can visit http://www.w3.org/Protocols/rfc2616/rfc2616.txt for more information on HTTP responses.

The body of the response contains markup outlining the procedure returned from its processing. This information is stored in a <methodResponse> element, which can contain a single <params> or <fault> element depending on the successfulness of the processing. For example, if no error occurred, you might see a result like the one in rpc-response-success.xml in Listing 1-12, which states that the user was accepted.

Listing 1-12 rpc-response-success.xml: A successful response from our procedure call.

<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
      <value>
        <string>Accepted</string>
      </value>
    </param>
  </params>
</methodResponse>

If an error occurred in the processing you might see something like rpc-response-error.xml shown in Listing 1-13, where the application validateUser.exe was not found.

Listing 1-13 rpc-response-error.xml: An error stating the application was not found on the system.

<?xml version="1.0"?>
<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>ErrorCode</name>
          <value>
            <int>1</int>
          </value>
        </member>
        <member>
           <name>ErrorMessage</name>
           <value>
             <string>Application not found.</string>
           </value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>

Invoking procedures, applications, and processes on remote systems offers a compelling reason to look into and potentially use XML-RPC. Remember that we will discuss SOAP later in this book, so before you make any decisions, be sure to understand the benefits and issues of both of these standards.

Application Messaging

The term "messaging" is often thought of as messaging between friends and family, such as with Microsoft Network Messenger, and not between full blown enterprise-level applications, which could in turn connect your data repositories to those of your partners and affiliates. XML changed all that, and with the help of standards like SOAP, application messaging is now a reality.

XML is used to describe the Application Programming Interface (API) to a Web Service, which allows applications to partially automate the process by which they communicate. The requesting application can understand the capabilities of the responding server and determine if it is compatible with its own data structure and type.

What Is Messaging?

The purpose of application messaging is for software applications, potentially in geographically different locations, to communicate in a near-automated fashion. Communicating usually involves receiving a request, processing that request, and a returning a result to the requesting application. XML emerges as the language of choice for describing not only the request, but also the response. This description might contain the path or route of the request, the type of information requested, and the actual data returned (marked up in XML, of course), but this is only part of the solution.

With the shift to Web Services as the next generation of Application Service Providers, XML is used not only for application-to-application messaging, but also for describing the services themselves. This description could contain anything from the type of data that can be requested to outlining the result options. Following this model, applications can query other applications to understand their capabilities before making a request.

A Simple Example of Messaging

News Feed, Inc. builds a system that returns the daily news to a requesting application. The system has everything from sports, financial news, and local news, to world, weather, and travel news. The application has the ability to be queried and pass a date to see if it has any relevant news for a given day. This query also contains the type of information you're looking for, what you want returned (count, headers, all data), and how many results are returned. The XML schema used to describe this query is located in Listing 1-14. In Chapter 7 you will see that standards exist that provide an entire framework for this type of communication, so treat this as a simple example of how you might implement a messaging system.

Listing 1-14 NFQuery.dtd: An XML schema for querying the News Feed system.

<?xml version='1.0' encoding='UTF-8' ?>
<!ELEMENT query (news)>
<!ELEMENT news EMPTY>
<!ATTLIST news  date  CDATA  #REQUIRED
          type   (global | local | financial |
                  sports | travel | weather )  #REQUIRED
          what   (count | headers | all )  #REQUIRED
         limit CDATA  #IMPLIED >

Joe's Sports Tips, a local newspaper that uses News Feed's data, only wants to retrieve sports news. Because of this, Joe's first sends a query to the News Feed system to see how many sports headlines it has for a given date. The query, contained in Listing 1-15, requests the number of sport headlines for October 10, 2001.

Listing 1-15 2001-10-10_sports_query.xml: The Joe's Sports Tips query of the News Feed system.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE query SYSTEM "NFQuery.dtd">
<query>
    <news date = "2001-10-10" type = "sports" what = "count"/>
</query>

The next part of the process is for News Feed to return a response to Joe's. The format of the response is governed by the NFResponse.dtd schema in Listing 1-16. The response, shown in Listing 1-17, shows 53 items in sports news on October 10.

Listing 1-16 NFResponse.dtd: A schema for News Feed's response.

<?xml version='1.0' encoding='UTF-8' ?>
<!ELEMENT results (count | headers | all)>
<!ATTLIST results  date CDATA  #REQUIRED
          type  (global | local | financial |
                 sports | travel | weather )  #REQUIRED >
<!ELEMENT count (#PCDATA)>
<!ELEMENT headers (#PCDATA)>
<!ELEMENT all (headline+)>
<!ELEMENT headline (#PCDATA)>

Listing 1-17 2001-10-10_sports_query_response.xml: The response from News Feed.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE results SYSTEM "NFResponse.dtd">
<results date = "2001-10-10" type = "sports">
    <count>53</count>
</results>

Now that Joe's knows how many items it can access, the system decides to request the first 10 articles. Again using the NFQuery.dtd data model, it changes the initial query to reflect that it wants 10 items and the entire article for these items.

Listing 1-18 2001-10-10_get_sports.xml: A request for the first 10 sport news stories.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE query SYSTEM "NFQuery.dtd">
<query>
    <news date="2001-10-10"
         type="sports" what="all" limit="10"/>
</query>

Several sections in this book discuss this type of application-to-application messaging and communication. Chapter 4 and Chapter 15 are dedicated to messaging and Microsoft's BizTalk Server, which provides all the components necessary to get you up and running, including Microsoft BizTalk Messaging Manager.

Process Modeling

A relatively new use of XML is to model processes and workflow. Remember the map.xml document earlier in this chapter? That document was a good example of steps, or a process, that needed to occur in a specific order. Not following this order, or the directions in our example, will result in someone getting lost.

Representing UML

Those of you familiar with the Unified Modeling Language (UML) know that it can be used for process modeling by outlining the flow from one step to another. Using the UML is a way you can describe a model in a language-agnostic way. The model could be an application, or maybe how a waiter takes your order at a restaurant. These models are made up of properties and behaviors. The important part of this approach is not that Step #2 comes after Step #1, but the relative relationship between these steps. Knowing that Step #1 is before Step #2 and is two steps away from #3 is just as important.

Because UML is quickly evolving as a standard way to represent models, and because XML is a standard used to describe them, many UML applications now support XML as an exportable format.

Using XML to model processes is not just a means by which you can describe how something works. Because it was implemented in XML, it inherits all the abilities of the XML language, such as validation. This provides a second level of verification when it comes to ensuring that processes are defined according to the rules and objectives set forth.


Microsoft .NET

Microsoft took the lead in developing a framework that allows for developing a new generation of software. This framework melds computing and communications in an innovative way, offering developers the tools they need to transform the Web and every other aspect of the computing experience. Microsoft dubbed this framework, which relies and is built heavily on the use of the XML, .NET (dot Net) framework.

For the first time a framework exists that allows developers, businesses, and consumers to harness technology on their terms. The creation of truly distributed Web Services that will integrate and collaborate with a range of complementary services to serve customers in ways that today's dotcoms can only dream of is one of the foundations of .NET. Applications based on the .NET Framework can make information available at any time, in any place, and on any device.

Microsoft's innovative work with .NET resulted in a powerful message and platform for application development and deployment. We will go over many aspects of .NET within this book, so we will introduce you to its concepts here.

Back to Fundamentals

The fundamental idea behind the .NET Framework is that the focus is shifting from individual Web sites or devices connected to the Internet to constellations of computers, devices, and services that work together to deliver richer, broader solutions. People will control what, when, and how information is delivered to them. Computers, devices, and services will collaborate with each other to provide rich services instead of being isolated islands where the user provides the only integration. Businesses will offer their products and services in a way that lets customers seamlessly embed them in their own electronic fabric.

The .NET Framework will help drive a transformation in the Internet that will see HTML-based presentation augmented by programmable XML-based information.

The Software as a Service Approach

Programming models previously focused on a single system. Current object models like the Component Object Model (COM) and the Common Object Request Broker Architecture (CORBA) rely heavily on model-specific protocols such as Distributed COM (DCOM), Remote Method Invocation (RMI), or Internet Inter-Orb Protocol (IIOP) residing in a homogenous infrastructure. On the other hand, the .NET Framework is explicitly designed to allow the integration of any group of resources on the Internet into a single solution. Currently this type of integration is extremely complex and costly. The .NET Framework will make the integration and assembly of software services intrinsic to all software development projects.

The loosely coupled XML-based .NET programming model introduces the concept of creating XML-based Web Services. Whereas today's Web sites are handcrafted and don't work with other sites without significant additional development, the .NET programming model provides a built-in mechanism to build any Web site or service so that it will coalesce and collaborate seamlessly with others. Just as the introduction of interchangeable components accelerated the industrial revolution, the .NET Framework promises to hasten the development of software as a service.

Web Services

Broadly speaking, a Web Service is simply an application that can be integrated with other Web Services using Internet standards. In other words, it's a URL-addressable resource that programmatically returns information to clients who want to use it. One important feature of Web Services is that clients don't need to know how a service is implemented. The ability, or methods from a programming standpoint, are exposed and can be discovered using a specific process, which allows applications to be more dynamic and therefore require less attention as time goes on.

Like components, Web Services represent black-box functionality that can be reused without worrying about how the service is implemented. Web Services provide well-defined interfaces, called contracts, which describe the services provided. Developers can assemble applications using a combination of remote services, local services, and custom code. For example, a company might assemble an online store using the Microsoft Passport service to authenticate users, a third-party personalization service to adapt Web pages to each user's preferences, and a credit card processing service.

Unlike current component technologies, however, Web Services do not use object model-specific protocols such as DCOM, RMI, or IIOP as the set of participants in an integrated business process, and as technology changes over time, it becomes difficult to guarantee a single, unified infrastructure among all participants. Web Services take a different approach: they communicate using ubiquitous Web protocols and data formats such as HTTP and XML. Any system supporting these Web standards will be able to support Web Services.

Using XML within .NET

XML is the obvious choice for defining a standard yet extensible language to represent commands and typed data, and it's only appropriate that Windows .NET has adopted its use widely within its framework. While rules for representing commands and typed data using other techniques (such as encoding this information in a query string) could be defined, XML is specifically designed as a standard metalanguage for describing data. SOAP is an industry standard for using XML to represent data and commands in an extensible way. SOAP is being further developed at W3C under the name of XML Protocol (XMLP).

XML is the enabling technology for the Web Service contracts. The Service Contract Language (SCL) is an XML grammar for documenting Web Service contracts. Because SCL is XML-based, contracts are easy for both developers and developer tools to create and interpret. Microsoft is also a contributor to the Web Services Description Language (WSDL), recently submitted to W3C for further development (see http://www.w3.org/TR/2001/NOTE-wsdl-20010315). Standards like SOAP, SCL, and Discovery Protocol (DISCO), an XML-based format for Web Service discovery, help developers because they do not need to understand and implement different ways to access each Web Service they use.

Obviously, the advantages of the model are many. Companies can not only more easily integrate internal applications, but they can also access services offered by other businesses. By combining Web Services on the Internet, companies can create a wide variety of value-added applications.


What Are the Next Steps?

Before you can really dive into XML you need to understand XML-based languages and know what software to use and where to get help. If you have previous experience with deploying XML solutions, the following parts will be a refresher. If you have experience with XML but have not developed any large-scale applications and systems using it, the items in this section should help you on your way. You will be reminded of important exposure you should have and about the kinds of applications you should consider using. Additionally, we have included places where you can get help if you need it.

Necessary Knowledge

Few people learn XML as their first markup-based language; most have had previous experience with SGML or HTML/XHTML. Even if you have no prior experience, XML is an easy language to learn conceptually. It is simply metadata used to describe a markup language. But conceptual understanding is not the biggest problem in knowing and using XML.

XML 1.0 is a means used to create the markup language. Saying that you know XML states that you can understand the constructs of a given XML-based language, but does not necessarily mean that you understand the purpose and usage of that language. While learning XML you should take the time to learn XML-based languages created with it. For instance, you should explore other standards such as Namespaces in XML, XPath, or SOAP. Your usage of XML will surely go beyond just the details of the language itself and will involve many of the other XML-based implementations.

Because understanding some of the more important XML-based languages is so important, we cover several in Appendix A. As you create your solution you will come across an instance where you will say, "Gee, I really need X," where X is a type of functionality not inherent in XML or your deployment environment. With research and exposure to other standards, you are sure to find something that performs at least 85 percent of what you are looking for, if not more.

The Universal Description, Discovery, and Integration (UDDI) was recently launched to host Yellow Pages-like entries for Web Services that are available via their defined protocol. For instance, if you want to find a company that could perform credit card validation by passing them an XML document, you should first check http://www.uddi.com to see who is currently offering this type of service.

XML Software

Using XML for personal or professional uses requires software. How much software you require is completely dependent on what you, from knowledge and programming standpoints, are comfortable with providing. You may opt to use a text editor, like Notepad, in writing your schemas and instance documents instead of performing these tasks in some kind of Integrated Development Environment (IDE).

You may also, in accordance with the published standards, opt to write your own parser for validating instance documents. Although this is unlikely, the application you build will be larger if a prebuilt parser is included. This could be anything from a type of user agent to a server that processes XML documents for insertion into a database.

We cover parsers in detail in Chapter 3 and touch on several popular XML software offerings in Appendix B. To give you a leg up until then, we listed several tools you might want to consider in Table 1-1.

Table 1-1 Tools of the XML Trade
Tool Function | Description
Schema and DTD | Tools used to create XML DTDs and schemas to describe data models. These tools usually follow the XML 1.0 or XML Schema Recommendations from the W3C ( http://www.w3.org ), or some other standard based on these.
Document instance | Tools that will create an XML document. These documents might only be well-formed, or they might also be validated against a DTD or schema.
Parsers | Applications responsible for parsing an instance document, potentially validating it against a DTD or schema, and then exposing the various aspects of the data in a programmatic view (that is, as an object or some other API). It will also check to make sure that the document conforms to the XML standard.
Application | Application that takes the document after parsing and performs a given task with the data. For instance, this could be a database that inserts the data in the instance document into the appropriate tables.

After you finish Chapters 2 and 3 and glance at Appendix B, you will not only be ready to move into the real meat of the book where you will build applications using XML, but you will also have a good idea as to what, if any, extra software you might want to use in your projects.

Where to Get Help

Obtaining help is one of the most overlooked and untapped benefits of today's computer world. Not only have complex applications provided detailed help files that are searchable like a mini-database (using the Help system in Windows is one great example), but the ability to use the Internet to search for similar or related problems makes it easy to find a solution. The combination of these resources alone gives developers a leg up on difficulties they might come across.

Several places on the Web offer you help, and several methods can help you get it. In Table 1-2, for instance, we included the URLs to the standards bodies that you should come to know and understand. While everyone is free to monitor and participate, they all provide a large number of resources, standards, and other information that you will find useful.

Table 1-2 Standards-Based Sites
URL | Description
http://www.w3.org | The World Wide Web Consortium is where many of the XML standards are housed, including the XML 1.0 Recommendation and the newly adopted XML Schema Recommendation.
http://www.oasis-open.org | The Organization for the Advancement of Structured Information Standards (OASIS) is like the W3C in many ways. It hosts standards and provides the framework to propose and produce a standard for paying members.
http://www.ietf.org | The Internet Engineering Task Force (IETF) is by no means focused on XML, but they do work that uses XML heavily. One particular item is their WWW Distributed Authoring and Versioning (WebDAV) group. Microsoft included support for WebDAV in applications such as BizTalk Server, so we recommend you stay alert on the IETF efforts.

If you are serious about following up on XML after reading this book, we suggest you try the sites listed in Table 1-3. You will probably find what you're looking for, or at least a pointer to it. For those of you using Microsoft servers and products for your implementation, the Microsoft Developer Network Online link (MSDN Online) should be of special interest because many questions can be answered there.

Table 1-3 Useful XML Sites
URL | Description
http://msdn.microsoft.com/workshop/xml/index.asp | A nicely done and thorough section of the MSDN Online site. It contains generic information on XML and related standards and specific information on XML support within their applications.
http://www.xml.com | XML.com, which is an O'Reilly supported site, offers free content in the form of articles, examples, and other resources for the new and seasoned XML programmer.
http://www.ibm.com/developerworks/xml | The developerWorks site provides tools, code, and examples on many open standards, one of which is XML. It provides basic self-help information and descriptions of tools and other helpful items available to those implementing XML.
http://www.xml.org | The XML.org site, which is hosted by OASIS, is an open forum for the discussion and exchange of information on XML. Its purpose is to distribute information they have assembled and collected about XML, tools, and communities that use it.

Finding help is not always easy, so if all else fails you can fall back on your favorite search engine and a few keywords to see if any additional sites cove your topic of interest. If you do take this approach, remember that you should use specific keywords to narrow your search to items that are likely to represent what you are actually seeking.


Where Do You Want to Go?

Developers historically built applications by integrating local system services. This model gave developers access to a rich set of development resources and precise control over how the application behaves. Developers moved beyond this model by building complex n-tier systems that integrate entire applications from all over their networks and add unique value on top. More is needed, however, and the flexibility of XML and the Internet creates the perfect platform to accomplish these goals.

In this chapter we have laid the foundation for using XML in many aspects of enterprise-level solutions. We have introduced you to the specifics that will be covered and carried throughout the book and into the examples. We have also seen a mix of technology and business in the application of these ideas and methods. We know that technology is the main focus for many of the readers, but at the same time, by providing this added insight into the business-side of the equation, we have prepared you to have an understanding beyond just the code you write. We attempted to answer that famous question "But, why?" that so many of us ask day to day.

Chapter 2 begins a section dedicated to discussing the details of XML and implementing it. This includes a refresher on the XML language and exposure to related standards based on XML, as well as applications you might want to consider within your implementations.


XML Basics

Although Chapter 1 is important for building the foundation you'll need for deploying XML solutions, in this chapter we begin our headfirst dive into the XML language. Because the main focus of this book is using XML and not the semantics, this chapter will be short and to the point. Our objective is simple: to provide you with a refresher course on the syntax and semantics of XML to carry you through the rest of the book.

As you know, the importance of XML in today's interconnected world demands that we fully understand our tasks, objectives, and milestones for projects. In the chapters that follow we will walk you through real-world examples of building applications and solving problems. First, however, we will fine-tune your knowledge of the language.


The Goals of XML

Before you can fully understand and appreciate what XML introduces into the application integration equations, we must take a moment to talk about the initial goals behind creating the language. The original XML Working Group at the W3C was formed out of the SGML Editorial Review Board, and it made 10 design goals while creating the XML language. According to the XML 1.0 Recommendation (http://www.w3.org/TR/2000/REC-xml-20001006#sec-origin-goals), these were as follows:

  1. XML shall be straightforwardly usable over the Internet.
  2. XML shall support a wide variety of applications.
  3. XML shall be compatible with SGML.
  4. It shall be easy to write programs which process XML documents.
  5. The number of optional features in XML is to be kept to the absolute minimum, ideally zero.
  6. XML documents should be human-legible and reasonably clear.
  7. The XML design should be prepared quickly.
  8. The design of XML shall be formal and concise.
  9. XML documents shall be easy to create.
  10. Terseness in XML markup is of minimal importance.

You should be exposed to these items because they lay the foundation for the design and objectives of the language. If you plan on using XML, you should make sure your objectives are in line with these design goals. Failure to do so could result in using XML for a purpose outside the bounds of the language, and therefore create a limiting implementation.

SGML was the parent standard for HTML. You can find more information on SGML at http://www.w3.org/MarkUp/SGML .


The XML Language

XML is a subset, or a pared down version, of SGML. If you are familiar with SGML you will notice the similarities. Unlike SGML, the XML syntax is simple, which is one reason why it has seen much success, especially over the last year. In this section we go over some basic language semantics and syntax that you need to understand, such as elements, entities, comments, and processing instructions.

In various parts of this chapter we will refer to the term "schema." We are not speaking of XSD, but of a simple data schema. Specific references to XSD are made using a capitalized S, such as in "Schema," or the abbreviation XSD.

Elements

Elements represent the tags, or language, that you create with XML. To define an element in a DTD you use the following syntax.

<!ELEMENT name type>

The name is the element name you want to define and the type is the type of content the element contains. This could be text, other elements, or a combination of the two. Here's an example to give you a better idea of what this means.

Suppose you want to create a customer schema that includes their name and contact information. To provide another level of granularity we break the name into first, last, and middle names, and contact into address and phone. Taking it one more step, we will break address into street, city, state, and zip, while we break phone into home, work, and mobile. Figure 2-1 is a visual representation of this data model.

Because declaration within XML DTDs must appear in a specific order, we first define our customer element. This element contains two child elements as its content, so the DTD representation of this XML element takes the following form:

<!ELEMENT customer (name , contact)>
Figure 2-1 A visual representation of our customer data model.

In this definition, for the document to be valid (more on what valid is later in the chapter), it must contain a single instance of the <name> element followed by a single instance of the <contact> element. You can impose some rules on this, such as having name OR contact or making one or both optional, but we will discuss those later.

Defining <name> and <contact> is similar because they are parent elements of child elements. So are <address> and <phone>. The definition for these can be represented as follows:

<!ELEMENT name (first , middle , last)>
<!ELEMENT contact (address , phone)>
<!ELEMENT address (street , city , state , zip)>
<!ELEMENT phone (home , work , mobile)>

The final step is to define the elements that actually hold the instance data. These include the child elements first, middle, last, street, city, state, zip, home, work, and mobile. We want these elements to hold only regular text, which is referred to as parsed character data, so we will define them as having #PCDATA. These definitions look like:

<!ELEMENT first (#PCDATA)>
<!ELEMENT middle (#PCDATA)>
<!ELEMENT last (#PCDATA)>
<!ELEMENT street (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ELEMENT home (#PCDATA)>
<!ELEMENT work (#PCDATA)>
<!ELEMENT mobile (#PCDATA)>

The last piece we need, which is actually the first item in our schema definition, is the <?xml> declaration. This is nothing more than

<?xml version='1.0' encoding='UTF-8' ?>

Our completed schema shown in Listing 2-1, customer.dtd, can now be referenced and used in instance documents such as johndoe.xml in Listing 2-2, which is displayed in Microsoft Internet Explorer in Figure 2-2. We added comments in the johndoe.xml file to help you understand what information is included.

Listing 2-1 customer.dtd: The XML definition of the customer data model.

<?xml version='1.0' encoding='UTF-8' ?>
<!ELEMENT customer (name , contact)>
<!ELEMENT name (first , middle , last)>
<!ELEMENT contact (address , phone)>
<!ELEMENT address (street , city , state , zip)>
<!ELEMENT phone (home , work , mobile)>
<!ELEMENT first (#PCDATA)>
<!ELEMENT middle (#PCDATA)>
<!ELEMENT last (#PCDATA)>
<!ELEMENT street (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ELEMENT home (#PCDATA)>
<!ELEMENT work (#PCDATA)>
<!ELEMENT mobile (#PCDATA)>

Listing 2-2 johndoe.xml: An instance of the customer.dtd document.

<?xml version = "1.0"?>
<!DOCTYPE customer SYSTEM "customer.dtd">
<customer>
 <!--(name , contact)-->
  <name>
    <!--(first , middle , last)-->
    <first>John</first>
    <middle>Smithy</middle>
    <last>Doe</last>
  </name>
  <contact>
    <!--(address , phone)-->
    <address>
      <!--(street , city , state , zip)-->
      <street>123 Some Street</street>
      <city>Anytown</city>
      <state>NC</state>
      <zip>25555</zip>
    </address>
    <phone>
      <!--(home , work , mobile)-->
      <home>919.555.1212</home>
      <work>919.555.1213</work>
      <mobile>919.555.1214</mobile>
    </phone>
  </contact>
</customer>
Figure 2-2 Loading our johndoe.xml document in Microsoft Internet Explorer.

Element Attributes

Sometimes an element needs to be annotated by adding additional information. This step is called "adding attributes." This information can be used for a variety of purposes, but most commonly falls within one of the following categories:

  • As a distinguishing factor on the type of element
  • Additive descriptive information
  • Information to be used by the application in processing the data

Attributes are fairly easy to add to schemas and are often shared across elements. The basic syntax for adding attributes is:

<!ATTLIST element  name datatype #use >

The element refers to the element that the attribute should be associated with, while the name is the name you give the attribute. In your document instances you will use a name="value" pair to assign attribute values.

The datatype can be one of three types. It can either be a string (CDATA), a set of tokenized types (ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, or NMTOKENS), or enumerated types. The string type can take any literal string, while the tokenized types have varying lexical and semantic constraints. Enumerated types can take one of a list of possible values. If you need a refresher on what the tokenized data types mean, go to http://www.w3.org/TR/2000/REC-xml-20001006#sec-attribute-types .

Finally the #use is a requirement for specifying if the attribute is required or not. If it is required, this will contain #REQUIRED, and if not, #IMPLIED. Because an element can have more than one attribute, you are able to repeat the name, datatype, and #use combination within the same <!ATTLIST> instance. To help you understand this, let's create a new version of our customer.dtd schema, called customer_v2.dtd, and add attributes. This new schema will then be used to create instance documents such as johndoe_v2.xml in Listing 2-3

We add a type attribute to our <customer> element and define it as an enumerated data type with the option of being either "current" or "past", which will require this attribute to be set. We also add a type attribute to our <mobile> element and define it as a string. This will point out how more than one element can have the same attribute name but with a different meaning. Finally we define a tokenized ID type for <customer>, which will allow us to identify this element by a character ID, though the attribute is not required.

To create these attributes we need to add the following lines to our schema.

<!ATTLIST customer  type  (current | past )  #REQUIRED
                    id   ID  #IMPLIED >
<!ATTLIST mobile  type CDATA  #IMPLIED >

As you can see, this is a simple task. You see the names of the elements we created attributes for, the type of attributes they are, and whether they are required or not. You should also notice how our enumeration type attribute for <customer> is specified within the parentheses. The | character is a choice, or an OR condition, between the possible values of type.

Listing 2-3 johndoe_v2.xml: An updated document instance supporting our new attributes.

<?xml version = "1.0"?>
<!DOCTYPE customer SYSTEM "customer_v2.dtd">
<customer type = "current" id = "abc">
  <name>
    <first>John</first>
    <middle>Smithy</middle>
    <last>Doe</last>
  </name>
  <contact>
    <address>
      <street>123 Some Street</street>
      <city>Anytown</city>
      <state>NC</state>
      <zip>25555</zip>
    </address>
    <phone>
      <home>919.555.1212</home>
      <work>919.555.1213</work>
      <mobile type = "phone">919.555.1214</mobile>
    </phone>
  </contact>
</customer>

Extra Flexibility

As we saw in the previous attribute example, conditions can be imposed on attributes. This functionality is also available in defining elements. You can represent OR statements and specify whether elements are optional or repeatable. Additionally, you can nest items within parentheses to add more flexibility. The lists below contain a list of the characters you should use to define these statements.

Character | Description
? | Optional
+ | Repeatable
* | Optional and Repeatable
| | OR

For example, let's create a customer_v3.dtd, building on our second version, which does the following:

  • <middle> element optional and repeatable
  • <home> element optional
  • <mobile> element optional

To create this variation we need to change two lines of code to the following:

<!ELEMENT name (first , middle* , last)>
<!ELEMENT phone (home? , work , mobile?)>

Now we can create johndoe_v3.xml as shown in Listing 2-4 with two middle names and no home or mobile number, and the document will be valid.

Listing 2-4 johndoe_v3.xml: Changing our example document instance to include two middle names and no home or work number.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE customer SYSTEM "customer_v3.dtd">
<customer type = "current" id = "abc">
  <name>
    <first>John</first>
    <middle>Smithy</middle>
    <middle>Anderson</middle>
    <last>Doe</last>
  </name>
  <contact>
    <address>
      <street>123 Some Street</street>
      <city>Anytown</city>
      <state>NC</state>
      <zip>25555</zip>
    </address>
    <phone>
      <work>919.555.1213</work>
    </phone>
  </contact>
</customer>

Entities

XML is made of entities and parsed or unparsed data. Entities are a single character construct or a collection of named constructs that are referenced in the document. Parsed data is made up of character data or markup and is processed by an XML processor. Unparsed data, on the other hand, is raw text not processed as XML. In this part we look at internal and external entity declarations and how they're referenced in instance documents.

Internal Entities

Internal entities are entities whose content is defined within the current schema. We focus on the definition first and then show you how to reference them.

Suppose you want to define an entity to hold the name of the author of the DTD so that it can be referenced in instance documents without explicitly typing it. The following code would construct this entity. By defining this entity, all an instance document author needs to do is place &dtd-author in the document to reference "R. Allen Wyke".

Processors and Parsers

We need to clarify the difference between a processor and a parser (which we will talk about in Chapter 3). A processor is a software module that reads an XML instance document and provides access to its content and structure. The parser is the part of the processor that analyzes the markup and determines the structure of the document data. If it is a validating parser it can also perform a validation of the structure against a DTD.

<!ENTITY dtd-author "R. Allen Wyke">

This entity instance is not the only type of entity that can be created within a schema and used in an instance document. It is also possible to define a list of elements or attributes as an entity so they can all inherit the same list. The syntax for defining groups of elements under a single name is

<!ENTITY % name "elements">

Just like in a content model for an element, you can have one or more elements defined in elements, and you can impose conditions using the characters listed on page 8. Defining reusable attribute entities, however, is slightly different. For these you must include whole attribute definition, which is accomplished using the <!ATTLIST> declaration, like in customer_v2.dtd.

<!ATTLIST % name  attrname datatype #use >
                  attrname datatype #use >

These attribute-lists entities also differ from attribute lists because they are referenced in the schema, not the instance document. This is accomplished by placing a % in front of the entity name.

To make sure we're all on the same page, let's build customer_v4.dtd on our version 3 schema and define some internal entities. We are going to

  • Define a dtd-author entity
  • Create an element entity called cus-basic that contains <name> and <contact>
  • Define <author>, <internal>, and <external> elements and define the content model of <internal> and <external> with %cus-basic
  • Replace the current content module of <customer> with a choice of <internal> or <external> AND include an <author> element instance
  • Define an attribute entity called attr-basic that contains ID and type
  • Include %attr-basic list as attributes for <customer>, <internal>, and <external>

The first step is to create the dtd-author entity, which we showed you how to do earlier. Next we need to add an element entity named cus-basic that contains <name> and <contact> and define three new elements. The content model for <internal> and <external> should include our newly defined cus-basic. These three steps will require the following additions to our schema.

<!ENTITY dtd-author "R. Allen Wyke">
<!ENTITY % cus-basic "name , contact">
<!ELEMENT internal (%cus-basic;)>
<!ELEMENT external (%cus-basic;)>
<!ELEMENT author (#PCDATA)>

Next we want to redefine the <customer> content model to include a choice of <internal> or <external> and an <author> instance. Following this step we will define an attribute entity list called attr-basic, which contains ID and type, and use it as the only attributes for <customer>, <internal>, and <external>.

<!ELEMENT customer ((internal | external) , author)>
<!ENTITY % attr-basic " type CDATA  #IMPLIED
                        id   CDATA  #IMPLIED">
<!ATTLIST customer  %attr-basic; >
<!ATTLIST internal  %attr-basic; >
<!ATTLIST external  %attr-basic; >

Figure 2-3 is a visual representation of our completed schema, and the DTD is shown in customer_v4.dtd.

Figure 2-3 A visual representation of our revised schema.

Here is what customer_v4.dtd contains:

<?xml version='1.0' encoding='UTF-8' ?>
<!ENTITY % attr-basic " type CDATA  #REQUIRED
                        id   CDATA  #IMPLIED">
<!ENTITY % cus-basic "name , contact">
<!ELEMENT customer ((internal | external) , author)>
<!ATTLIST customer  %attr-basic; >
<!ELEMENT name (first , middle* , last)>
<!ELEMENT contact (address , phone)>
<!ELEMENT address (street , city , state , zip)>
<!ELEMENT phone (home? , work , mobile?)>
<!ELEMENT first (#PCDATA)>
<!ELEMENT middle (#PCDATA)>
<!ELEMENT last (#PCDATA)>
<!ELEMENT street (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ELEMENT home (#PCDATA)>
<!ELEMENT work (#PCDATA)>
<!ELEMENT mobile (#PCDATA)>
<!ATTLIST mobile  type CDATA  #IMPLIED >
<!ENTITY dtd-author "R. Allen Wyke">
<!ELEMENT internal (%cus-basic;)>
<!ATTLIST internal  %attr-basic; >
<!ELEMENT external (%cus-basic;)>
<!ATTLIST external  %attr-basic; >
<!ELEMENT author (#PCDATA)>

Listing 2-5 johndoe_v4.xml: A sample document using the new schema.

<?xml version = "1.0"?>
<!DOCTYPE customer SYSTEM "customer_v4.dtd">
<customer type = "current" id = "xyz">
  <internal type = "current" id = "xyz">
    <name>
      <first>John</first>
      <middle>Smithy</middle>
      <middle>Smithy</middle>
      <last>Doe</last>
    </name>
    <contact>
      <address>
        <street>123 Some Street</street>
        <city>Anytown</city>
        <state>NC</state>
        <zip>25555</zip>
      </address>
      <phone>
        <work>919.555.1213</work>
      </phone>
    </contact>
  </internal>
  <author>&dtd-author;</author>
</customer>

Figure 2-4 shows a sample document, johndoe_v4.xml, loaded into Microsoft Internet Explorer. Notice how the parser replaced the entity reference to dtd-author with the string "R. Allen Wyke".

Figure 2-4 The dtd-author entity reference replaced.

External Entities

External entities are what the name implies: entities defined in external files. This file is "imported" using the <!ENTITY> declaration and includes a name, identifier (SYSTEM or PUBLIC), and a literal (URI or path), in the form:

<!ENTITY name identifier "literal">

Although we will not go into much detail about external entities in this chapter, the detail you should note is that this is the method you use to include other elements, like images, schemas, and the like, into your schema.

A good example of how this might be used is if you want to build your schema in modules. If you wanted to include customer_v4.dtd as part of another schema so that it would inherit the data model, you could include the following:

<!ENTITY % customer_v4.dtd SYSTEM "customer_v4.dtd">

If customer_v4.dtd is not in the current working directory, you need to be sure to include a path to the file. Additionally, if you have stored your schema on a server accessible to your customers or partners, you could replace SYSTEM with PUBLIC and include the URI in the schema.

<!ENTITY % customer_v4.dtd PUBLIC
    "http://www.microsoft.com/dtds/customer_v4.dtd">

Comments

Comments within your XML schemas and documents are useful and important, as they are in any type of language. Although the ability to create human-readable markup is one of the objectives of XML, sections, elements, attributes, or other items might need more description.

XML follows the syntax HTML uses for comments. They begin with <!-- and end with -->. A few examples follow. In the second example we show how these comments can span multiple lines.

<!-- here is a one line comment -->
<!-- here is a comment
     that spans more than one line
-->
The XML grammar does not allow your comment to end with ---> (three hyphens). This violates the document's requirement to be well-formed, which we discuss at the end of the chapter.

Processing Instructions

Another function of XML is to pass processing instructions within the document. The thought behind this functionality is the ability to remove, as much as possible, any software-specific markup that your schema might require. This prevents any need to include elements and attributes in your schema that do not increase the description of the data, keeping only what it should be used for by the application.

As an example, say you're passing an instance of the customer_v4.dtd schema to an application that will transform the schema into another XML dialect, like a Microsoft BizTalk compatible. Say this application is a Web Service and is accessible through a URL. To include this reference in your schema you could have something like the following, where dtd2biztalk is the target name and href="http://www.microsoft.com/scripts/dtd2biztalk.exe" is the instruction your application would understand:

<?dtd2biztalk
    href="http://www.microsoft.com/scripts/dtd2biztalk.exe?>


Document Instances

The XML 1.0 Recommendation states that XML documents can come in two forms: well-formed and valid. A well-formed document is generally more flexible, or less strict, than a valid one. As we will discuss in the next section, this type of document needs only to have the proper beginning and ending tags as well as proper nesting to be considered an XML document. Valid documents, on the other hand, must conform in accordance to the referenced DTD. You should understand both types because both are acceptable in XML.

Well-Formed Documents

Well-formed XML conforms to the Recommendation by fulfilling several requirements:

  • There can be only one root element.
  • All open elements must be properly closed (that is, if a start tag is present, there should be an end tag, or if the element is empty, it should be appropriately tagged as such).
  • Any nested elements should be ended in correct order.

The first requirement is a simple one: there can be only one root element. For instance, if the two-roots.xml document in Listing 2-6 was building off the customer_v4.dtd schema, it would not be well formed.

Listing 2-6 two-roots.xml: A well-formed XML document with only one root element.

<?xml version = "1.0"?>
<!DOCTYPE customer SYSTEM "customer_v4.dtd">
<!-- being the root element -->
<customer type = "current" id = "xyz">
  <internal type = "current" id = "xyz">
    <name>
      <first>John</first>
      <middle>Smithy</middle>
      <middle>Smithy</middle>
      <last>Doe</last>
    </name>
    <contact>
      <address>
        <street>123 Some Street</street>
        <city>Anytown</city>
        <state>NC</state>
        <zip>25555</zip>
      </address>
      <phone>
        <work>919.555.1213</work>
      </phone>
    </contact>
  </internal>
  <author>&dtd-author;</author>
</customer>
<customer type = "past" id = "xyz">
  <external type = "past" id = "xyz">
    <name>
      <first>Jane</first>
      <last>Doe</last>
    </name>
    <contact>
      <address>
        <street>123 Some Street</street>
        <city>Anytown</city>
        <state>NC</state>
        <zip>25555</zip>
      </address>
      <phone>
        <work>919.555.1220</work>
      </phone>
    </contact>
  </external>
  <author>&dtd-author;</author>
</customer>

Figure 2-5 shows the error reported by Internet Explorer 5.5, which contains an XML parser that will check for a well-formed (but not valid) document. As you can see, this document is not well formed because it has two instances of the <customer> element and no single governing root element.

Figure 2-5 The error reported by Internet Explorer 5.5 because the document has multiple root elements.

The second requirement is that all open elements should be properly closed. If we build on the same example but remove the second instance of <customer> and leave off our ending </customer> element, we again violate the rules for a well-formed document. To demonstrate, we will load the example in Listing 2-7, missing-closing.xml, into Internet Explorer 5.5.

Listing 2-7 missing-closing.xml: Missing the closing </customer> tag and therefore not well formed.

<?xml version = "1.0"?>
<!DOCTYPE customer SYSTEM "customer_v4.dtd">
<customer type = "current" id = "xyz">
  <internal type = "current" id = "xyz">
    <name>
      <first>John</first>
      <middle>Smithy</middle>
      <last>Doe</last>
    </name>
    <contact>
      <address>
        <street>123 Some Street</street>
        <city>Anytown</city>
        <state>NC</state>
        <zip>25555</zip>
      </address>
      <phone>
        <work>919.555.1213</work>
      </phone>
    </contact>
  </internal>
  <author>&dtd-author;</author>

Because Internet Explorer 5.5 contains a parser that validates whether documents are well formed, loading the previous document results in the error shown in Figure 2-6. Do not worry about parsers at this point. They will be covered in detail in Chapter 3.

Figure 2-6 Internet Explorer generating an error because an ending tag is missing.

The last requirement of a well-formed document is that any nesting of elements must occur in the proper format. For this example we will use XHTML because improper nesting of HTML elements was commonly accepted until the release of XHTML. In nested-error.html, shown in Listing 2-8, the <strong> element improperly ends before the <em> element.

Well-formed documents have few requirements for conforming to XML 1.0, but they can be useful and helpful in your projects. However, because much of your work will also demand a specific element structure, as well as any required attributes or elements, you might need documents that are not only well formed, but valid as well.

Listing 2-8 nested-error.html: Incorrectly ending the <strong> element before the nested <em> element.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
      xml:lang="en">
<head>
  <title>Core XML</title>
</head>
<body>
<p>
  Sometimes you <strong>really <em>need</strong></em>
  to make sure you do the right thing.
</p>
</body>
</html>

Valid and Non-Valid Documents

The second way to verify XML documents is to compare their structure to a governing DTD or schema. A DTD or schema can define everything from the structure of the document to the data types, required attributes, and other requirements for how elements and attributes can be included in the document.

If any part of the document does not conform to the referenced DTD, it is not a valid document. This method of verifying documents is most often used in industry standards or application-to-application communication. These types of environments demand that the documents they exchange conform to a specific model, because without such a requirement the risk of processing invalid data is high and could adversely affect the entire system.

Using the customer_v4.dtd schema, we impose the requirement that the <customer> element should contain a type attribute value of either current or previous. If we exclude this value, as we did in johndoe_v5.xml, and run the document through a parser, checking only to see if the document is well formed, the document will be processed without any warning or error.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE customer SYSTEM "customer.dtd">
<customer id="xyz">
    <name>
        <first>John</first>
            <middle>Smithy</middle>
        <last>Doe</last>
    </name>
    <address>
        <line1>123 Some Street</line1>
        <line2>P.O. Box 555</line2>
        <city>Anytown</city>
        <state>NC</state>
        <zip>55555</zip>
    </address>
    <phone>
        <home>5551212</home>
        <work>5551213</work>
    </phone>
    <online>
        <email>john@doe.com</email>
        <url>http://www.doe.com</url>
    </online>
</customer>

However, if we ran the same example through a parser checking for validity, such as Xerces from the Apache group (http://xml.apache.org), we get an error complaining that the required type attribute is not included.


Moving Forward

Although this book focuses on the overall use of XML and not XML syntax, we've covered many important details in this chapter. We have essentially gone through a crash course on the XML language, and we have pointed out some of the details we will be using in later chapters. Our overall objective here was to refresh the memory of you experienced XML developers, while at the same time providing a baseline for those of you new to the language.

At this point we are ready to jump into implementing XML within a large enterprise environment. The first step in this process is to determine the parser you want to use, and therefore the parsing method, which will be covered in Chapter 3.


Parsing XML Documents

Using XML to solve integration and implementation problems is not as easy as defining a data model and creating instance documents. What will you do with these documents after you create them? How will you read them into your application or database for processing? Part of using XML as a solution involves an XML processor, which is the application responsible for processing these documents. One function of this processor, and the focus of this chapter, is parsing documents. The parser is responsible for parsing the XML document and verifying it by checking for well-formedness or by validating it against a schema. If these tasks are performed successfully, the data contained within the document is exposed in a method that makes it available for other manipulations.

Parsing is not always as simple as just reading through an XML document and verifying it for ASCII text. The structure and rules of your governing DTD can be verified when processing these instance documents if yours is a validating parser. You need this parsing application to evaluate the instance document and determine if it's valid and then make it available for secondary applications to utilize the data contained therein.

In this chapter we'll review what an XML parser does, examine the different models used to process XML documents, and introduce how XML can be manipulated using objects within the .NET Framework. By the end of the chapter you should have a good understanding of when to use which processing model, as well as which parser will best fit your needs.

So, what is parsing? According to http://www.dictionary.com, it's defined as follows:

  1. To break (a sentence) down into its component parts of speech with an explanation of the form, function, and syntactical relationship of each part
  2. To describe (a word) by stating its part of speech, form, and syntactical relationships in a sentence
  3. To examine closely or subject to detailed analysis, especially by breaking up into components: "What are we missing by parsing the behavior of chimpanzees into the conventional categories recognized largely from our own behavior?" (Stephen Jay Gould)
  4. To make sense of; comprehend: I simply couldn't parse what you just said
  5. Computer Science. To analyze or separate (input, for example) into more easily processed components

Parsing is an essential task for any application that uses language-based data or code as input. XML processors, which rely heavily on parsers, provide a standard mechanism for navigating and manipulating XML documents. If you have an XML document and need to get data out of it, change the data, or modify the XML document structure, you don't need to write code to load the XML file, validate it for specific characters and elements, and process this information accordingly. You can use an XML parser instead, which will load the document and give you access to its contents in the form of objects.


What Does an XML Parser Do?

Parsing a language refers to the process of taking a piece of code or data written in that language and breaking it into component parts as defined by the rules of that language. XML parsers are classified along two independent dimensions: validating vs. nonvalidating and stream based vs. tree based.

Validating and Non-Validating Parsers

A validating parser can use a DTD or schema to verify that a document is properly constructed according to the rules for the XML application it's an instance of, and it is supposed to complain loudly if the rules aren't followed. A DTD can also specify default values for the attributes of various elements, and a validating parser can fill them in when it encounters elements with no attributes listed. This capabililty can be important when you're processing XML documents you've received from the outside world. For example, if vendors send XML-marked invoices to your company, you'll want to ensure that they contain the right elements in the right order.

A non-validating parser only requires that the document be well-formed. Because of the design of XML, it's possible to parse well-formed documents without referring to a DTD or XSD schema. Additional information for being well-formed, which was discussed in Chapter 2, can be found in the XML 1.0 Recommendation ( http://www.w3.org/TR/2000/REC-xml-20001006).

Non-validating parsers are simpler, and many of the free parsers available over the Web are non-validating. They are usually adequate for processing XML documents generated within the same organization or documents whose validity constraints are so complex that they can't be expressed by a DTD and need to be verified by application logic instead.

Stream-Based and Tree-Based Parsers

A parser can make the components of an XML document known to an application in two ways. It can read through the document and signal the application every time a new component appears, or it can read the entire document and give the application a tree structure corresponding to the element structure of the document. Parsers that use the first method are called stream-based or event-driven parsers. Parsers that use the second method are tree-based parsers. Both methods will be discussed in greater detail later.

You'll hear two common terms regarding these methods: Simple API for XML (SAX) and the Document Object Model (DOM). SAX is a standard developed informally by members of the xml-dev mailing list for how a stream-based parser should "talk" to an application (see http://www.megginson.com/SAX/index.html). The DOM is a formal Recommendation of the W3C on how an application can access and manipulate the tree structure of a document (for an example see http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001 where the first edition is referred to as Level 1).


Tree-Based Parsing with the DOM

The DOM API defines a minimal set of language and platform-independent interfaces for accessing and manipulating the content and structure of information stored in XML documents. In this section we will cover DOM's major interfaces and briefly touch on the minor ones.

In tree-based parsing with the DOM the document is checked to see if it is well-formed and valid, depending on the type of parser. The parser then converts the document's information into a tree of nodes. The entire document, no matter how simple or complex, is converted into a tree that starts from one root node, which, in DOM terms, is called a document object instance (hence Document Object Model). Once a document object tree is created, access to the elements allows you to modify, delete, and create leaves and branches by using the interfaces in the API.

We are using Titles.xml as the example XML file during this discussion. This file, shown in Listing 3-1, presents a collection of books based on the sample pubs database that comes with Microsoft SQL Server.

Listing 3-1 Titles.xml: A sample XML document.

<?xml version="1.0" encoding="UTF-8"?>
<BookList>
    <Book>
      <book_id>BU1111</book_id>
       <title>Cooking with Computers: Surreptitious Balance Sheets</title>
      <type>business</type>
      <pub_id>1389</pub_id>
      <price>11.95</price>
      <advance>5000</advance>
      <royalty>10</royalty>
      <ytd_sales>3876</ytd_sales>
      <notes>Helpful hints on how to use your electronic...</notes>
      <pubdate>1991-06-09T05:00:00</pubdate>
    </Book>
    <Book>
      <book_id>BU7832</book_id>
      <title>Straight Talk About Computers</title>
      <type>business</type>
      <pub_id>1389</pub_id>
      <price>19.99</price>
      <advance>5000</advance>
      <royalty>10</royalty>
      <ytd_sales>4095</ytd_sales>
       <notes>Annotated analysis of what computers can do for you</notes>
      <pubdate>1991-06-22T05:00:00</pubdate>
    </Book>
</BookList>

Figure 3-1 is a visual representation of how Titles.xml can be represented as a tree of nodes.

Figure 3-1 A DOM hierarchy representation of Titles.xml.

Everything is a node in the Document object tree. These nodes might have child nodes or hold information like its tag name (nodeName) and value (nodeValue). This hierarchical organization of information is similar to a file system, where folders might contain files or other folders, except everything descends from one root folder.

Important Interfaces in the DOM

The DOM provides interfaces in its hierarchy of Node objects. The interfaces either have child nodes that contain other nodes or are leaf nodes that do not contain anything after them in the document structure. Some types of child or leaf nodes are Node, Element, and NodeList, all of which are interfaces in the DOM.

Node

An XML Document object created after a DOM parser reads an XML file often contains a tree-like representation of Node objects instances, while other interfaces are provided to create a more object-oriented environment. You can manipulate all the information in the DOM by using the Node interface. Even though the DOM Recommendation specifically states that it isn't necessarily a tree, for the purposes of the discussions in this chapter and the examples therein we will focus on the tree-like representations. Figure 3-2 shows the inheritance relationships between some of the important interfaces.

Figure 3-2 DOM interfaces and inheritance relationships.

Because the Document object is a subclass of Node, the root Node object of the tree is also a Document object. Every DOM object must have a root. Figure 3-3 illustrates a sample XML Document object tree and describes some of the Node objects that it contains.

Figure 3-3 A Document object, where everything in the DOM is a Node.

You can find out if a Node has children by using the hasChildNodes( ) method. This method, which takes no parameters, returns a Boolean true if the node has children and false if not.

The getNodeType( ) method, which is part of the Java bindings defined by DOM, is another important method of Node. It returns the type of a particular Node. The type is a constant integer used to identify different types of Nodes. For example, the Node.ELEMENT_NODE type identifies a Node to be an element. Table 3-1 contains a list of the other methods available for the Node object as well.

Table 3-1 Other Methods of the Node Object
Method | Description
appendChild( ) | Adds a new child object, which is passed to the method, to the current Node.
cloneNode( ) | Returns a duplicate of the Node.
hasAttributes( ) | Returns a Boolean true if the Node has any attributes. This method was added in DOM Level 2.
insertBefore( ) | Takes a new child Node and a reference child Node and inserts the new child Node before the reference Node.
isSupported( ) | Tests whether or not this implementation of the DOM supports a specific feature. This method was added in DOM Level 2 and takes a version number and a feature as parameters.
normalize( ) | Puts all text nodes in the full depth of the sub-tree underneath this Node.
removeChild( ) | Removes the specified child.
replaceChild( ) | Replaces the specified child with the new child passed.

Element

The Element interface, which is a subclass of Node, is another important interface. It can be used to access the elements in a DOM Document object tree, which allows you to read in attributes and their values, as well as change, delete, or add to them. Table 3-1 contains the list of methods of the Element object.

Table 3-2 Methods of the Element Object
Method | Description
getAttribute( ) | Retrieves the specified attribute.
getAttributeNS( ) | Retrieves the specified attribute by local name and namespace. This method was added in Level 2.
getAttributeNode( ) | Retrieves an Attr node by name.
getAttributeNodeNS( ) | Retrieves an Attr node by local name and namespace. This method was added in Level 2.
getElementsByTagName( ) | Returns a NodeList of all child elements of a given tag name in the order in which they are encountered.
getElementsByTagNameNS( ) | Returns a NodeList of all child elements of a given tag by local name and namespace in the order in which they are encountered. This method was added in Level 2.
hasAttribute( ) | Returns a Boolean true if the specified attribute is present. Returns Boolean false otherwise.
hasAttributeNS( ) | Returns a Boolean true if the specified attribute, by local name and namespace, is present. Returns Boolean false otherwise. This method was added in Level 2.
removeAttribute( ) | Removes the specified attribute.
removeAttributeNS( ) | Removes the attribute specified by local name and namespace. This method was added in Level 2.
removeAttributeNode( ) | Removes the specified Attr node.
setAttribute( ) | Adds a new attribute. If an attribute of the same name exists, its value is changed to the specified value.
setAttributeNS( ) | Adds a new attribute. If an attribute of the same local name and namespace exists, its value is changed to the specified value. This method was added in Level 2.
setAttributeNode( ) | Adds a new Attr node. If an Attr node of the same name exists, its value is changed to the specified value.
setAttributeNodeNS( ) | Adds a new Attr node. If an Attr node of the same local name and namespace exists, its value is changed to the specified value. This method was added in Level 2.

NodeList

Some methods of the Node interface allow traversal of a Node tree. The getChildNodes( ) method is useful for gathering all the elements inside a Node. This method returns all Nodes, if they exist, in a container for Node objects. NodeList is an iterator for a list of Nodes. Figure 3-4 illustrates a NodeList.

Unlike Node and Element, NodeList has only a single method, item( ). This method returns the Node located at the indexed position passed to the method. For instance, if you want to retrieve the first Node, you call the method using item(0).

Other DOM Interfaces

Node, Element, and NodeList are not the only interfaces specified by the DOM. Because we do not cover all of them, we've included a list in Table 3-3 along with any children interfaces they have and a brief description. This table contains only those interfaces found in the DOM Level 1 and 2 Core and does not contain the HTML bindings.

Table 3-3 DOM Interfaces
Interface | Children | Description
Attr | Text, EntityReference | Represents an attribute of an Element object.
CDATASection | Contains no children | Used to escape characters of text that would otherwise be considered markup.
Comment | Contains no children | Stores the content of an XML or HTML comment.
Document | Element (max. of one), ProcessingInstruction, Comment, DocumentType (max. of one) | Represents the entire XML or HTML document.
DocumentFragment | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference | A minimal Document object instance.
DocumentType | Contains no children | Represents the document type stored in the doctype attribute of the Document object instance.
Element | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference | Represents an element in an XML or HTML document.
Entity | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference | Represents an entity, whether it's parsed or unparsed.
EntityReference | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference | Can be inserted into a structured model when an the entity reference is in the source document or when you want to insert a reference.
Notation | Contains no children | Represents a notation declared in a DTD.
ProcessingInstruction | Contains no children | Used to represent a "processing instruction."
Text | Contains no children | Contains textual content (character data).
Figure 3-4 A DOM NodeList Object.


Stream-Based Parsing with SAX

One of the major disadvantages of the DOM is how it processes large files. Because the DOM requires the entire file to be read in by the parser, memory can constrain the performance of your applications, if not render them useless. SAX parsers solve this problem by streaming in the document according to specific events. In this section we cover the behavior of a SAX parser and how to use one.

The Behavior of a SAX Parser

Unlike the DOM, which creates a tree-based representation, SAX doesn't have a default object model. When you use a SAX parser and read in a document, you will not be given a default object model. These parsers only read in your XML document and fire events based on the following:

  • Open or start of elements
  • Closing or end of elements
  • #PCDATA and CDATA sections
  • Processing instructions, comments, and entity declarations

Three Steps to Using SAX

The three steps to using SAX in your applications are

  1. Creating a custom object model, like a Book class
  2. Creating a SAX parser
  3. Creating a document handler to turn your document into instances of your custom object model

Because SAX does not come with a default object model representation for the data in your XML document, you need to create your own the first time you use this method. The model could be something as simple as creating a Book class if your XML document is an address book.

After your custom model is created to hold your data in your application, the next step is creating a "document handler" to initialize instances of your object models from the document. This document handler is a listener for the various events we listed that are fired by the SAX parser. Most of the work involved in using SAX is in creating these document handlers.

As the SAX parser reads a document, events are fired based on all the "registered" document event listeners and translated into method calls on your document handler implementation. The document handler must then do something useful with these method calls.

Figure 3-5 shows the sequence of method calls the SAX parser makes on your document handler implementation. You can see from this picture how the SAX parser exposes the document as a series of events that are translated into method calls in your document handler implementation.


Choosing a Parsing Method

You should choose your parser depending on the nature of the processing and the size of the XML documents. A tree-based parser usually needs to load the entire document into memory, so it can be impractical because of physical constraints on memory when processing documents like dictionaries or large databases. With a stream-based parser you can skip over elements that you aren't interested in (for example, when looking up a particular word in a dictionary). If your application needs to process certain elements in relation to other elements, however, a tree-based parser is much easier to work with. It's worth noting that a tree-based parser can be built on top of a stream-based parser and that the output of a tree-based parser can be "walked" to provide a stream-based interface to an application. In this section we cover the DOM and SAX parsing methods and provide example scenarios in which you can decide which method is appropriate for a given task.

Figure 3-5 A SAX Event Order.
The term "walked" refers to taking pieces of the document and sending them out in parts. You are traversing, or walking, the document objective model.

The DOM Method

DOM implementations are currently biased toward in-memory storage of the document, but this may change as Persistent DOM (PDOM) implementations become more popular. Even with memory limitations, however, DOM certainly has a place because of features that help it access and manipulate documents. The following are DOM benefits you should focus on:

  • It allows random access to the document.
  • Complex searches can be easily implemented.
  • The DTD or schema is available.
  • The DOM is read/write.

The first two benefits are the ability to randomly access the document and create complex searches. These provide a means for searching for elements and retrieving information, such as data and attributes, on these elements. The DOM can also be bound to an XML DTD or schema, which means it can be checked to make sure the data contained in the document is valid according to the rules of the DTD or schema. Finally it provides the ability to read data out of a document and write data to it.

The DOM's simplicity, powerful access to the document, and a well-defined specification make it a popular parser method. It also pairs well with XSLT and other document-transformation solutions you might require. Therefore, if your project is small and you need to complete it quickly, using a DOM-based method is a great choice. However, if you are going to process large files and have the time to write a more robust application, you should look into a SAX-based implementation.

The SAX Method

If you need to parse and process huge XML documents, SAX implementations offer some benefits over DOM-based ones. You should first ask yourself, however, if an improved design would remove the need for large documents. For example, prefiltering in a database that can stream XML might suit your needs. By going with SAX, however, you can enforce options for document manipulation by using XSLT and requiring your team to write code to internally manage, store, and rewrite the document.

Like the DOM, SAX has a particular set of benefits. The following list contains some of the most useful:

  1. It can parse files of any size.
  2. You can build your own data structure.
  3. You can access only a small subset of the information if you desire.
  4. It is fast.

The biggest advantage of SAX is, arguably, its ability to process files of any size. The way the parser streams data in and out (exposes data) allows it to handle files of any size. SAX is also useful when you want to build your own data structure and allows you to grab only subsets of the information in a given document. Finally it can be a fast method of processing documents, especially when parsing large files.

SAX is best suited to sequential-scan applications when you want to go through the XML document quickly from start to finish. Also, sometimes you won't need the overhead of the full-blown DOM, so a SAX parser will be sufficient for creating a lightweight and compact internal data structure.

Example Scenarios

To help you choose a method we included a few example scenarios. While processing and using XML documents is more widely adopted every day, many lessons have gone unnoticed because of lack of experience. These scenarios should help by allowing you to walk down a decision path and choose the right approach. With these benefits in mind, review the following scenarios and determine which parser is appropriate for each one.

Scenario 1

Company XYZ currently has 20,000 employees. The Human Resources data file is currently stored in XML format, and you are to write an application that returns the average annual salary of all employees.

If you use the DOM interface your application will need to load the entire employee database into memory and retrieve the document.employee[i].annual_salary[0] value for each employee, and then average the values.

Using the SAX approach you could write an event handler that looks for only the <annual_salary> element and ignores everything else. You could parse through the file systematically and efficiently. The solution in this scenario is clear: SAX makes one pass through a large file and looks for specific data.

Scenario 2

Company ABC has 375 employees. The Human Resources data file is stored in XML format, and you are to write an application that allows users to scroll through the list of employees and find detailed information on an employee.

If you tried this with the SAX approach you would either have to parse through the XML document every time you wanted to display information to the user, which is inefficient, or you would have to build your own memory structure so that you could parse it once and then access it multiple times. You'd need to keep track of all the information yourself and develop and maintain the code required to support this data storage scheme.

By using the DOM you have access to the entire employee database as nodes in a tree. The data storage mechanism and the code that supports it is essentially provided by the parser. This is a much easier solution!

Scenario 3

If we return to the first scenario, let's say you're asked to modify your application to give 7 percent raises to those employees below the company average and 4 percent to those above it.

Your application would need to be parsed with the DOM. SAX does not support modification of data. Even if it did, as an event-based parser it would be difficult to write two sets of event handlers: one to calculate the average and one to update the data as it's parsed for the second time.


Available Parsers

Now that you've seen the two main parsing methods, let's talk about a few of the parsers you can use for processing, in particular Microsoft's MSXML, the Apache Group's Xerces, and a few others. Each of these comes as a library that can be added to applications, such as a server that can accept XML documents via HTTP posts, or is part of existing applications.

MSXML

The first parser, which can perform both SAX and DOM-based parsing, is Microsoft's MSXML. This parser, which is currently at version 3.0, supports several standards and can handle most of your parsing needs. Some of the supported standards include

  • XSLT
  • XML Path Language (XPath)
  • SAX2

MSXML is available as a Microsoft Windows library file (DLL) that can be embedded into applications for processing. The first version of this parser appeared in Microsoft Internet Explorer 4 and late versions of Windows 95. You should know what version of the parser you are using because different versions support different standards. For this reason, we included Table 3-2, which contains the parser version, filename, and file version.

In addition to these library files, Microsoft also embedded its parser in several of its applications. Table 3-5 provides a list of these applications, their corresponding Internet Explorer versions, if applicable, and the MSXML version.

Beginning with the beta release of MSXML 3.0, the installation of the msxml3 DLL was performed in a "side-by-side" mode—the original, older version was left intact. Additionally, Microsoft Internet Explorer and Microsoft Windows 95 and later versions would continue using the older parser library.

To upgrade your system to utilize the version 3.0 library, it's necessary to run the xmlinst.exe application that can be downloaded from the Microsoft Download Center ( http://msdn.microsoft.com/downloads/default.asp?URL=/code/sample.asp?url=/msdn-files/027/001/469/msdncompositedoc.xml). The application, when passed the appropriate arguments, will remove the registry entries for the older parser and replace them with the version 3.0 entries, thereby updating the system.

Table 3-4 MSXML Versions
Parser Version | File Name | File Version
1.0 | msxml DLL | 4.71.1712.5
1.0a | msxml DLL | 4.72.2106.4
1.0 Service Pack 1 (SP1) | msxml DLL | 4.72.3110.0
2.0 | msxml DLL | 5.0.2014.0206
2.0a | msxml DLL | 5.0.2314.1000
2.0b | msxml DLL | 5.0.2614.3500
2.5 Beta 2 | msxml DLL | 5.0.2919.38
2.5a | msxml DLL | 5.0.2919.6303
2.5 | msxml DLL | 5.0.2920.0
2.5 Service Pack 1 (SP1) | msxml DLL | 8.0.5226
2.6 January 2000 Web Release | msxml2 DLL (January Web Release) | 7.50.4920.0
2.6 Beta 2 | msxml2 DLL | 8.0.5207.3
2.6 | msxml2 DLL | 8.0.6518.1
3.0 March 2000 Web Release | msxml3 DLL (March Web Release) | 7.50.5108.0
3.0 May 2000 Web Release | msxml3 DLL (May Web Release) | 8.0.7309.3
3.0 July 2000 Web Release | msxml3 DLL (July Web Release) | 8.0.7520.1
3.0 September 2000 Web Release | msxml3 DLL (September Web Release) | 8.0.7728.0
3.0 Release | msxml3 DLL | 8.0.7820.0
Table 3-5 XML Versions Shipped with Microsoft Products
Application | Internet Explorer | MSXML Version
Not applicable | Internet Explorer 4.0 | 1.0
Windows 95, OEM Service Release 2.5 | Internet Explorer 4.0a | 1.0a
Not applicable | Internet Explorer 4.01, Service Pack 1 (SP1), or Internet Explorer 5.0 | 2.0
Microsoft Office 2000 | Internet Explorer 5.0a | 2.0a
Windows 98, Second Edition | Internet Explorer 5.0b | 2.0b
Windows 95, Windows 98, or Windows NT 4.0 | Internet Explorer 5.01 | 2.5a
Windows 2000 | Internet Explorer 5.01 | 2.5
Windows 2000 | Internet Explorer 5.01, Service Pack 1 (SP1) | 2.5 Service Pack 1 (SP1)
Windows 95, Windows 98, Windows NT 4, Windows 2000, or Windows 2000 Service Pack 1 (SP1) | Internet Explorer 5.5 | 2.5 Service Pack 1 (SP1)
Microsoft SQL Server 2000, Beta 2 |  | 2.6 Beta 2
SQL Server 2000 |  | 2.6
Microsoft BizTalk Server (technology preview and beta) |  | 2.6

Embedding MSXML in Your Applications

After getting MSXML installed you'll want to start using it in your applications. This section covers how to use MSXML from Microsoft Visual Basic, starting with DOM and then SAX2. The DOM example code will build the tree in memory and will then access nodes in the memory structure. The SAX2 example responds to events while the XML document is parsed.

Using DOM

By choosing DOM you assume that you won't have any problem storing the entire XML tree in memory. You will also be able to make changes to the XML and save those as a file. In this example you will create a simple DOM object that catalogs the salaries of all the employees and gives everyone a 4 percent raise. The XML code is as follows:

<payroll>
<employee>
<last_name>Smith</last_name>
<first_name>John</first_name>
<salary>60000</salary>
<performance>Excellent</performance>
<title>Accounts Payable Manager</title>
   </employee>
</payroll>

First you'll need to instantiate the DOM Document object.

Dim xmlDoc As DOMDocument
Set xmldoc = New DOMDocument
xmldoc.validateOnParse = True
xmldoc.async = False

The first two statements probably look straightforward. The third statement asks the parser to validate the document when it's parsed. The last statement declares that the document shouldn't be loaded asynchronously. This means that you don't have to worry about whether the XML is fully loaded before you attempt to read and manipulate the code.

Now that you've created the object, load the XML. You can do this by loading a URL, a local file, or the XML text itself. This load( ) method will take either a URL or a path as its argument. This code snippet loads the XML from the intranet.

xmldoc.load("http://my.intra.net/salary.xml")

To load the XML directly you use the xmldoc.loadXML( ) method.

xmldoc.loadXML("<payroll><employee><last_name>Smith
</last_name><first_name>John</first_name><salary>60000</salary>
<performance>Excellent</performance><title>Accounts Payable Manager
</title></employee></payroll>")

For our example it doesn't make any sense to use the loadXML( ) method because we can access the data URL. However, remember that XML makes a good data structure for your internal programming needs. If another part of your application system needs to store some data in a tree structure, it might be efficient for it to generate XML and pass it to you as a string. Afterward you would load it into your DOMDocument using loadXML.

Now that you have the DOMDocument created and loaded with real data, you can start giving people raises. The first step is to set up a loop to iterate through all the nodes. In this example you need to locate the salary nodes and increase the value by 4 percent.

Set ElemList = xmldoc.getElementsByTagName("SALARY")
  For i=0 To (ElemList.length -1)
ElemList.item(1).text=Str$(Val(ElemList.item(1).text)*1.04)
  Next

With the salary increases in place, the next step is to write the new XML out to disk by using the following code:

xmldoc.save("NewSalary.xml")

You may not want to give everyone the same raise. In the following example, employees who perform at an "Excellent" level are the only ones who will get raises. The getNodes( ) method takes advantage of Extensible Stylesheet Language (XSL) patterns, which are covered in more detail later in the book.

Set ElemList = xmldoc.getNodes("PAYROLL/EMPLOYEE[PERFORMANCE=
'EXCELLENT']")
  For i=0 To (ElemList.length -1)
ElemList.item(1).text=Str$(Val(ElemList.item(1).text)*1.04)
  Next

Using SAX

In the previous example you were able to read all of your data in memory and you were also able to manipulate the data and save a new XML file. If you're using SAX, you probably aren't able to store all of the data in memory and you loose the ability to save the data. This section covers how to use the SAX interface of MSXML by writing a content handler. The content handler will be fired when a particular node is encountered.

For this example you'll use the same XML file as before. Instead of giving people raises, your code will calculate the total payroll for your company. The first step is creating a class that implements the IVBSAXContentHandler interface. This can be done with Microsoft Visual Basic 6 as follows:

  1. Download and install MSXML if it isn't already installed.
  2. Create a new project (for these examples, create a standard .EXE).
  3. In the "Available Resources" list, select Microsoft XML version 3.
  4. Create a new class and select IVBSAXContentHandler from the interface's drop-down list.

This example implements two methods: startElement( ) and characters( ). When the opening salary tag is encountered, a Boolean is set so that when the next characters call is made, the salary value is grabbed and added to the total.

Option Explicit
Implements IVBSAXContentHandler
Dim total As Integer
Dim salaryTag As Boolean
Total = 0
SalaryTag=False
Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As_
  String,_strLocalName As String, strQName As String, ByVal attributes As_
  MSXML2.IVBSAXAttributes)
        If strLocalName = "SALARY"then
        SalaryTag= True
    End if
End Sub
Private Sub IVBSAXContentHandler_characters(text As String)
     TotalPayroll=totalPayroll+Val(text)
        SalaryTag=False
End Sub

Xerces

Another popular parser is available from the Apache Group, an open source movement that made its name with a Web server, and it can be downloaded from http://xml.apache.org. The Xerces implementation, like MSXML, comes as a library, but it is available in three languages. These are a C++ library, a set of Java classes, and a COM and Perl binding/wrapper for the C++ implementation.

Xerces supports DOM Level 1 and 2 and SAX2. While it does not support some of the additional standards that MSXML does, if you need a common parser across various platforms and environments, Xerces might be the choice for you.

Developing applications with Xerces is similar to developing applications with any open source package: you download, follow the instructions for installation, write a sample to make sure everything is set up correctly, and then consult online documentation and news groups for information about how to build your particular application. In this example you'll learn how to get started with the Java implementation of Xerces.

You might find some minor differences between the C++ and Java implementations, so be aware of this during testing.

The first step is to download the Xerces distribution and make sure that xerces.jar is registered on your class path. While you are at the http://xml.apache.org site you might want to browse the API documentation and FAQs at http://xml.apache.org/xerces-j/api.html and http://xml.apache.org/xerces-j/faqs.html. To test your installation you can try writing a very simple application.

import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
public static void main(String args[]) {
    DOMParser parser = new DOMParser();
    try {
                 parser.parse(args[0]);
                 Document document = parser.getDocument();
        NodeList salaries = document.getElementsByTagName("SALARY");
        for (int i=0;i++;i<salaries.getLength()) {
            Node salary=salaries.item(i);
            String orgSalary=salary.getNodeValue();
            int newSalary=1.04*Integer.parseInt(orgSalary);
            salary.setNodeValue(""+newSalary);
            }
          } catch (SAXException e) {
                 System.err.println (e);
               } catch (IOException e) {
                 System.err.println (e);
              }
    System.out.println("Parsing done!");
    System.out.println(document.toString());
}
}

This application will parse the document and print Parsing done! and the new XML, or it will print an error. If you are able to compile and run this example, your installation succeeded. You've also taken your first steps in using the DOM functionality of Xerces. The next example uses SAX to total the salaries of all employees. As with the Visual Basic example, you need to implement a content handler to receive the incoming events:

import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;
import java.io.IOException;
public class PayrollCalculator implements DefaultHandler {
private int totalPayroll=0;
private boolean inSalaryElement=false;
public PayrollCalculator(String input) {
    SAXParser parser=new SAXParser();
    Parser.setContentHandler(this);
    Try {
        Parser.parse(input);
          } catch (SAXException e) {
        System.out.println(e);
          } catch (IOException e) {
        System.out.println(e);
         }
    }
public void startElement(String uri, String local, String qName, Attributes atts) {
    if (local.equals("SALARY")) {
        inSalaryElement=true;
        }
public void characters (String text) {
    if (inSalaryElement) {
        totalPayroll=totalPayroll+Integer.parseInt(text);
        inSalaryElement=false;
        }
    }
public int getTotalPayroll() {
    return totalPayroll;}
}
public void static main(String args[]) {
    PayrollCalculator calc=new PayrollCalculator(args[0]);
    System.out.println("Total payroll == "+calc.getTotalPayroll());
    }
}

Other Parsers

MSXML and Xerces are not the only parsers available. Within the Java community alone, programmers have a multitude of choices. Because DOM Level 1 and SAX2 are the heavyweights, you should ensure that your parser supports these methods. Table 3-3 contains a list of parsers written in Java and tells whether they are validating or not and what standards they support.

Table 3-6 Other DOM and SAX Parsers Written in Java
Parser | URL | Validating | Standards Supported
IBM's XML for Java | http://www.alphaworks.ibm.com/formula/xml | Yes | DOM Level 1, DOM Level 2, SAX 1, SAX 2, and Namespaces
Microstar's lfred | http://www.opentext.com/services/content_management_services/xml_sgml_solutions.html#aelfred_and_sax | Namespace validation only | DOM Level 1, DOM Level 2, SAX 1, and SAX 2
Sun's Java API for XML | http://java.sun.com/products/xml | Yes | DOM Level 1, SAX 1, and Namespaces
Oracle's XML Parser for Java | http://technet.oracle.com/ | Yes | DOM Level 1, SAX 1, and Namespaces


Parsing XML Within the .NET Framework

The Microsoft .NET Framework and ADO.NET provide a unified programming model to access data represented as both XML data (text delimited by tags that structure the data) and relational data (tables consisting of rows and columns). This framework reads XML data from any data stream into DOM node trees, where data can be accessed programmatically, while ADO.NET provides the means to access and manipulate relational data within a DataSet object. Here we look at the three key components for data access in ADO.NET and the .NET Framework, as defined in Table 3-7.

Within this table XmlDataDocument is a subclass of the XmlDocument. A .NET application can use the XmlDataDocument to load either relational or XML data and manipulate the data using the DOM. When the XmlDataDocument loads relational data, it synchronizes data between the DataSet and the DOM. After an XmlDataDocument loads relational data, an application can access the data programmatically using the .NET Framework.

Table 3-7 Key Components for Data Access
Component | Description
DataSet | Represents a relational data source in ADO.NET.
XmlDocument | Implements the DOM in the .NET Framework.
XmlDataDocument | Unifies ADO.NET and the .NET Framework by repre-senting relational data from a DataSet and synchro-nizing it with the XML document model.

The classes that comprise the .NET Framework and ADO.NET enable three broad scenarios. In Table 3-4 we cover these scenarios.

Table 3-8 Scenarios Enabled by the Classes
Scenario | Description
Accessing relational data with ADO.NET | Create and use a DataSet to access relational data without using XML. Read and write XML data to or from a DataSet without using an XmlDataDocument . (Any XML information not relevant to the relational presentation is lost.)
Accessing XML documents | Create and use an XmlDocument to access XML data streams. Use structured data in an XmlDataDocument without tables, rows, columns, relationships, and other relational elements. This makes it identical to the XmlDocument.
Accessing Relational Data in XML Documents (XML and ADO.NET) | Structure relational data in an XmlDataDocument with a DataSet. Obtain a DataSet from an XmlDataDocument to relationally view and query the structured content of an XML document. Work with the XML view of relational data in a DataSet. Provide relational and XML-based components for a data application. For example, you can edit and expose relational data without affecting the fidelity of an XML document and apply XSLT to relational data. Performs XPath queries over relational data.


Conclusion

We covered a lot of information on parsers in this chapter. We looked at the parser's responsibility, different parsing methods, and different parsers. Although you might not be writing a parser as part of your efforts, understanding how they work and how to choose one is important. You will require a parser to properly—and by a true implementation definition, "absolutely"—implement XML solutions. The function of parsing documents, be it for making sure a document is well-formed or for schema validation, is a task you want to leave to another application. Additionally, being able to specify the method by which you have access to the document's data is an advantage and a huge step toward completing your task.

In this chapter we touched on both the DOM and SAX standards, and we will dedicate Chapter 4 to other, related standards. Many groups use XML as a means to accomplish a variety of tasks. These projects are often the same tasks you are attempting to overcome, so take the time to go over a few of them.


Database Integration

So far we've covered the basics of XML and parsing XML documents. While these topics lay the foundation for using XML, they do not show you how to really apply XML. This chapter begins our exploration of using XML for solving real-world problems by focusing on a usage currently in the spotlight: using XML with databases.

We will begin this chapter with a short introduction to some important database-related history and concepts. We will also look at why XML is used with databases and discover some of the issues that can arise through the use of XML with databases. We will then take a closer look at using XML with one particular type of database, namely the relational database, where you will learn some of the common techniques used for exposing and persisting XML documents with databases. Finally, we will explore the features and capabilities provided by Microsoft SQL Server 2000 and Oracle 9i for supporting XML.


Databases and XML

One of the computer's most powerful applications is the ability to store, organize, and retrieve large quantities of data. An organized collection of data is referred to as a database, and the programs designed to manage databases are known as Database Management Systems (DBMSs).

In this era of information explosion, DBMS has evolved into the foundation of virtually all of an organization's information management. Performing a substantial information search without using a DBMS is rare indeed.

In this section we will take a quick look at some popular DBMSs. We will also briefly introduce some of the issues in using XML with databases.

Types of DBMS

A number of different types of DBMS have emerged over the years. The most significant difference between them is the model used to store, manage, and query databases. Besides affecting what software you need to acquire, the model used affects the way you will think about data and can be surprisingly difficult to change later. Let's take a quick look at some of the more popular DBMSs, their designs, their advantages, and their disadvantages.

Hierarchical DBMS

Widely used during the mainframe era, a Hierarchical DBMS (HDBMS) links records, also called nodes, together like a family tree such that each record type has only one owner. For example, an order is owned by only one customer. Figure 4-1 shows a sample hierarchical database containing customers and the orders they've placed.

Figure 4-1 An HDBMS example.

The Orders hierarchical database shown in Figure 4-1 has six nodes of type Customer and six of type Order. These nodes are linked together by pointers that the user must explicitly specify. For example, Order (0706) is linked to Customer (055). All the nodes linked together form a strictly defined tree structure.

The major disadvantage of a hierarchical database is that it can be hard to query flexibly in ways that go against the tight coupling of the data hierarchies. For example, a query to retrieve the Item node of Item No 000763 in the Orders database in Figure 4-1 might look like the following:

055/0706/000763

Note that this query follows a precise path to the required node. (The acute reader will notice that this is very similar to how one would use XPath to traverse to a node in XML). However, if you wanted to know all the Orders that also used the Item of this ItemNo, the only way to discover this would be through an extremely costly search of every Order node.

Relational DBMS

The relation database model was first proposed by Dr. E. F. Codd of IBM in his seminal paper titled "A Relational Model of Data for Large Shared Data Banks" in 1970. A Relational DBMS (RDBMS) models data as a set of tables where each table consists of a fixed collection of columns, or fields. An indefinite number of rows, or records, can occur within each table. Compared to the hierarchical model, the relational model is complicated. However, the relational model provides a much more flexible framework for data access and manipulation. You can ask any question you want of an RDBMS, but for an HDBMS you can ask only the questions programmed into the system.

Figure 4-2 shows the relational database structure of three example tables: Customer, Order, and Item.

Figure 4-2 An RDBMS example.

Unlike hierarchical databases, relationships between the tables in a relational database are built at runtime by linking key columns from one table to another. The database uses two types of key columns. The first one, called a primary key, is mandatory for every table in a relational database and is used to uniquely identify rows in a table. The second type, called a foreign key, corresponds with the primary key of other tables to form a parent-child relationship. For example, in Figure 4-2, CustomerNo is the primary key column of the Customer table, while OrderNo is the primary key column of the Order table. The Order table also has a foreign key column, CustomerNo, which links to the CustomerNo column of the Customer table. Therefore, in this case the Customer table is said to be the parent and Order the child.

The process of linking tables together at runtime using key fields is called joining the tables. One of the key features of relational databases is that they require few assumptions about how tables can be joined and data extracted. This flexibility allows the same database to be viewed and used in different ways.

In addition, because a single relational database can be spread across several tables, the database can be designed so that data redundancy is minimized. This is known as normalization, and the objective is to isolate data so that additions, deletions, and modifications can be made in just one table and propagated throughout the rest of the database based on the defined relationships.

Finally, true RDBMSs use Structured Query Language (SQL) to query, extract, and update data stored in databases and conform the data as closely as possible to the theoretical relational rules of normalization. For example, to find all Items ordered by Customer John, you can use the following SQL query:

SELECT ProductNo, Desc
  FROM Customer, Order, Item
  WHERE Customer.CustomerNo = Order.CustomerNo
  AND Order.ItemNo = Item.ItemNo
  AND Customer.FirstName = `John'

Object-Oriented DBMS

Object orientation for a database means the capability of storing and retrieving objects in addition to mere data. Objects are bundles of data and behaviors. Because the data in an object can be just another object or collection of objects, objects are similar to the nodes of an HDBMS, which likewise contains a collection of child nodes.

The ability to query objects is often provided through a set of methods inherited from a parent object provided by the Object-Oriented DBMS (OODBMS) framework. Each object can implement these methods in a manner appropriate for its purposes. A SQL, called OQL, that allows for the querying of these systems in a standardized way, has been proposed by the Object Database Management Group (ODMG) ( http://www.odmg.org ).

One of the biggest advantages of OODBMSs is their direct integration with object-oriented programming languages. Another important advantage is that the amount of data that can be modeled by an OODBMS is increased, and modeling this information is easier.

Why XML?

Over the past five years there has been an explosion in demand for access over the Internet to the data stored in enterprise databases and for the ability to use the databases to support electronic business operations. These include transactions between systems within an enterprise ("enterprise integration"), between businesses in a supply chain ("B2B e-commerce"), and directly to customers ("B2C e-commerce"). Because so many types and brands of DBMSs exist, two parties involved in the business transactional exchanges will likely be using incompatible DBMSs. On the other hand, even if they are using the same DBMS, their schemas might be so different that offering systems direct access to the other database can turn out to be complicated at best and disastrous at worst. If, miraculously, the two parties are using the same schemas, the protocols used by the DBMSs might not be accessible or even secured over a public network like the Internet.

Therefore, what many organizations are striving for is universal access to data in a fashion that is easy to manage and in a format that is self-describing and standards compliant. To help facilitate this goal, several industry vendors are turning to XML.

The Challenges of Using XML with a DBMS

Even though XML offers many advantages as a universal data-exchange format, using it with a DBMS can be problematic. For example, the way XML data is structured (that is, data modeling) can be radically different from the structure in many databases. This means that some sort of data model mapping must be used before XML can be retrieved from or stored in databases. In this section we will take a brief look at some of the issues that can arise through the use of XML with a DBMS.

Data Modeling

A data model is a conceptual representation of data structures. The data structures include the data objects, the relationships between the objects, and the rules that govern which operations can be performed on the objects.

Before we explore the problem with data modeling, you should realize that XML documents can be categorized into two major types: data-centric and document-centric. This categorization is important because it will often govern what's possible and what isn't when using XML with a DBMS.

Data-Centric XML Documents

The main characteristics of a data-centric XML document are a more organized and regular structure and a lack of (or very little) mixed content. Data-centric XML documents are designed for application consumption and application-to-application data exchange. Common examples include invoices, stock quotes, product catalogs, and application configuration files.

Listing 4-1 is an example of a data-centric XML document used to hold contact information for a personal phonebook. Notice that every information item, such as the full name, and the zip or postal code, is represented by an element, and there is no mixed content.

Listing 4-1 data_centric.xml: A sample data-centric XML document.

<?xml version="1.0"?>
<Contacts>
  <Contact ContactNumber="197459531">
    <FullName>Abercrombie, Kim</FullName>
    <CompanyName>Tailspin Toys</CompanyName>
    <EMail>akim@tailspintoys.com</EMail>
    <PhoneNo type="business">1-415-123-1234</PhoneNo>
    <Address>
      <Street>123 Main St.</Street>
      <City>San Francisco</City>
      <State>CA</State>
      <PostalCode>94116</PostalCode>
      <CountryCode>US</CountryCode>
    </Address>
  </Contact>
  <Contact ContactNumber="250021025">
    <FullName>Chen, John Y.</FullName>
    <CompanyName>Contoso, Ltd</CompanyName>
<EMail>johny.chen@contoso.com</EMail>
    <PhoneNo>852-1234-5678</PhoneNo>
    <Address>
      <Street>99 Dry St.</Street>
      <City>Cardiff</City>
      <PostalCode>CF17XX</PostalCode>
      <CountryCode>UK</CountryCode>
    </Address>
  </Contact>
</Contacts>

Document-Centric XML Documents

Document-centric design represents a more liberal use of free-form text that is "marked-up" with elements. Document-centric XML documents are usually meant for human consumption and are characterized by a less regular structure with plenty of mixed content. Examples are books, letters, e-mails, and HTML/XHTML documents. Listing 4-2 is an example of a document-centric XML document. Notice that while some parts of the document may look well-structured (<number>, <author>, and so forth), mixed content is present within the <bullet> and <para> elements.

Listing 4-2 document_centric.xml: A sample document-centric XML document.

<?xml version="1.0"?>
<chapter>
  <number>4</number>
  <title>Sample Chapter</title>
  <author email="john.kelly@thephone-company.com">Kelly, John
</author>
  <contents>
    <header level="1">
      <para>This is a sample paragraph.</para>
      <list>
        <bullet>This is <ref link="#12">item 1</ref></bullet>
        <bullet>This is <ref link="#13">item 2</ref></bullet>
      </list>
      <header level="2">
        <para>This is paragraph with embedded <bold>formatting
</bold>.</para>
      </header>
    </header>
  </contents>
</chapter>

Modeling Issues

On the surface XML appears to fit in the hierarchical modeling category. In practice, XML is a hybrid. XML is probably most similar to object modeling because it can be regarded as consisting of nodes, and nodes can contain heterogeneous data. On the other hand, document-oriented XML documents can have a high degree of heterogeneity of nodes (almost any element can appear almost anywhere) that few database models are able to replicate in terms of flexibility. In fact, it can be argued that the verbosity and formatting looseness of XML is the opposite of the strategies most DBMSs use to maximize performance and reliability.

Because of all these issues, most DBMS vendors provide indirect support for XML. Support for retrieving data-centric XML documents is usually generated dynamically from the underlying data models using predefined mapping rules. Some vendors support persisting the data-centric XML documents, and they use similar mapping rules to translate XML into data models supported by the DBMS. The biggest problem with this approach is that perfect round-tripping of a document (that is, storing the data from a document in the database and then reconstructing the document from the data) might not be possible because of the difference in data representation.

The complexity of providing a modeling framework that can handle the sheer flexibility involved means that most DBMSs do not provide any special built-in support for storage and retrieval of document-centric XML documents. Some DBMSs allow the user to store XML documents as Binary Large Object (BLOB) columns designed for storing unstructured or binary data. This approach gives you the usual advantages provided by databases, such as transactional control, security and centralized access. More sophisticated DBMSs have XML-aware tools with full-text query capabilities (such as proximity, synonym, and fuzzy searches) for the XML documents stored in the databases.

Data Types

Strictly speaking, text is the only data type supported by an XML Document Type Definition (DTD). Most databases, however, have strong data type support. Therefore, RDBMSs will need to convert data from text in the XML document to their native types in the databases and vice versa. The biggest problem with this is that no universal standard exists for textual representation of the different types of data. Dates are especially problematic because the range of possible formats is enormous.

Fortunately, W3C XSD provides a richer set of data types than DTDs, including number and date, and allows users to derive their own data types. RDBMSs that support XSD can therefore provide a more accurate representation of a database table schema using XML.

Binary Data

Because everything in XML is textual, binary data must be encoded before they can be included in an XML document. The two most common ways of encoding binary data in XML are unparsed entities and Base64 encoding. Unfortunately, if you're using a DTD, no standard XML notation exists for indicating that an element contains Base64-encoded data, so software might not recognize that the data is binary-encoded. With RDBMSs that support XSD, however, users can use the base64Binary data type to indicate that a certain data unit contains encoded binary data.

Null Values

Data with null values has a special meaning in the database world. They're used to represent such factors as uninitialized columns, invalid values, or data that simply isn't there. Note that this is different from a value of 0 for numbers or zero length strings. For example, two columns of null values are not considered equal, while two zero length strings are.

In XML optional elements and attributes can be used to represent null values. If a database value is null, it is simply not included in the corresponding XML element or attribute. Note that, just as with databases, empty values (such as zero length string) are represented by elements or attributes without any content (for example, <element /> or attribute="").

Instead of leaving out optional elements and attributes, some RDBMSs allow the user to define what constitutes null in an XML document, including supporting the XSD xsi:null type attribute.

Charsets

A charset is a byte-sequence-to-character mapping method and its associated data structures. For most Western European languages the charset, specified by the ISO-8859-1 standard (also called Latin-1), is simply a one-to-one byte-to-character mapping table. For other languages a different charset might be required. For example, for the Hebrew alphabet the ISO-8859-8 charset can be used. Some languages, though, might require more complex mapping structures and rules. For example, for some Asian languages that contain thousands of characters, including Chinese, Korean, and Japanese, a charset that uses multibyte characters is required. The EUC-KR charset is used for Korean, and the Shift_JIS charset is used for Japanese. Finally, the Unicode standard provides a single 16-bit charset that can be used to represent practically all the common languages used in the world today.

A charset is not the same as a character set. For a full explanation of the difference, check out RFC 2278 at http://www.ietf.org/rfc/rfc2278.txt.

Being text, XML is designed to support different charsets, including Unicode. Databases, on the other hand, often offer limited support at best for charsets. Most RDBMSs can support only one charset for the entire table or even the entire database. Therefore, retrieving and storing XML documents with databases might sometimes require an intermediate charset conversion step.


Using XML with Relational Database Management Systems

RDBMSs have emerged as the predominant DBMS available today. Corporations large and small depend on extremely reliable and scalable RDBMSs to manage the data required by applications for automating their business processes. Because of their popularity in the business world, most XML database integration projects are implemented with RDBMSs. In this section we will explore the different ways that RDBMSs have evolved to adopt support for XML.

Retrieving and Storing Data-Centric XML Documents

To retrieve and store data-centric XML documents, most RDBMSs provide a mapping mechanism to allow the transformation of relational data to and from XML data. Broadly speaking, the different mapping mechanisms provided can be categorized as two types: resultset-based mapping and schema-based mapping. These two mapping methods allow you to dynamically discover and represent your database structures from and into XML documents.

Resultset-Based Mapping

Resultset-based mapping models XML documents based on the tabular nature of the resultset generated by SQL queries. The columns and rows in a resultset are usually mapped as either XML elements or attributes or a combination of both. Consider Listing 4-3, a simple SQL query that returns records from the Employee table.

Listing 4-3 sample_query1.sql: A simple SQL query that returns a list of employee records.

SELECT Employee_ID, First_Name, Last_Name, Dept_No
  FROM Employee

This query will generate a resultset that might look like that shown in Figure 4-3.

Figure 4-3 A sample resultset returned from the SQL query shown in Listing 4-3.

To transform this into an XML document, we can decide to map the rows as elements of the XML document and the columns as attributes of these elements. An example of such an XML document is shown in Listing 4-4.

Listing 4-4 sample_query1.xml: A sample XML representation of the Employee table shown in Figure 4-3.

<?xml version="1.0"?>
<ROOT>
  <row Employee_ID="1" First_Name="Adam" Last_Name="Barr" Dept_No="3"/>
  <row Employee_ID="2" First_Name="Katie" Last_Name="Jordan" Dept_No="2"/>
  <row Employee_ID="3" First_Name="Scott" Last_Name="MacDonald" Dept_No="1"/>
  <row Employee_ID="4" First_Name="Tim" Last_Name="O'Brien" Dept_No="2"/>
  <row Employee_ID="5" First_Name="Sunil" Last_Name="Koduri" Dept_No="3"/>
  <row Employee_ID="6" First_Name="Laura" Last_Name="Norman" Dept_No="2"/>
</ROOT>
To ensure that the XML document is well-formed, we have included a root element that we call <ROOT>. One alternative naming convention is to use the name of the table, Employee in this case, as the root element name.

For each row in the resultset, we generated a <row> element in the XML document. The columns are then mapped to attributes of these <row> elements. Another way to map columns is to generate the child elements of the <row> elements, as shown in Listing 4-5.

Listing 4-5 sample_query2.xml: Same as Sample_Query1.xml, but using elements instead of attribute values to represent records.

<?xml version="1.0"?>
<ROOT>
  <row>
<Employee_ID>1</Employee_ID>
<First_Name>Adam</First_Name>
<Last_Name>Barr</Last_Name>
<Dept_No>3</Dept_No>
  </row>
  <row>
<Employee_ID>2</Employee_ID>
<First_Name>Katie</First_Name>
<Last_Name>Jordan</Last_Name>
<Dept_No>2</Dept_No>
  </row>
  <row>
<Employee_ID>3</Employee_ID>
<First_Name>Scott</First_Name>
<Last_Name>MacDonald</Last_Name>
<Dept_No>1</Dept_No>
  </row>
  <row>
<Employee_ID>4</Employee_ID>
<First_Name>Tim</First_Name>
<Last_Name>O'Brien</Last_Name>
<Dept_No>2</Dept_No>
  </row>
  <row>
<Employee_ID>5</Employee_ID>
<First_Name>Sunil</First_Name>
<Last_Name>Koduri</Last_Name>
<Dept_No>3</Dept_No>
  </row>
  <row>
<Employee_ID>6</Employee_ID>
<First_Name>Laura</First_Name>
<Last_Name>Norman</Last_Name>
<Dept_No>2</Dept_No>
  </row>
</ROOT>

One advantage of resultset-based mapping is that the generated XML document automatically reflects the live data and schema information present in the resultset from which the XML document is generated. This means that, by controlling the number of columns and rows in a resultset using standard SQL constructs, you automatically restrict the amount of data that will be included in the resultant XML document. In addition, aliased and computed columns will also be reflected in the XML document. For example, the following SQL query will generate a resultset that can be transformed into an XML document:

SELECT CONCATENATE( LastName, ` `, FirstName ) AS FullName
  FROM Employee
WHERE LastName = `Jordan'

The document would then look like Listing 4-6.

Listing 4-6 sample_query3.xml: XML representation of a resultset with computed columns.

<?xml version="1.0">
<ROOT>
  <row Employee_ID="2" FullName="Jordan, Katie"/>
</ROOT>
Only the row matching the condition specified in the WHERE clause is included in the XML document. Also, we've automatically picked up the FullName alias from the resultset and used it in the XML document.

Resultset mapping will function just as effectively for a resultset that consists of rows originating from more than one table. Consider the following multijoin SQL query as an example:

SELECT Dept.Dept_ID,
       Dept.Dept_Name,
       Employee.Employee_ID,
       CONCATENATE( Employee.Last_Name, ' ', Employee.First_Name ) AS Employee_Name
  FROM Dept, Employee
  WHERE Dept.Dept_ID = Employee.Dept_ID
  ORDER BY Dept.Dept_ID, Employee.Employee_ID

This query will generate a resultset that looks like that shown in Figure 4-4. We can easily transform this resultset into the XML document in Listing 4-7.

Figure 4-4 A multitable resultset.
Dept_ID | Dept_Name | Employee_ID | Employee_Names
1 | IT | 3 | McDonald, Scott
2 | HR | 2 | Jordan, Katie
2 | HR | 4 | O'Brien, Tim
3 | HR | 6 | Norman, Laura
3 | Operations | 1 | Barr, Adam
4 | Operations | 5 | Koduri, Sunil

Listing 4-7 sample_query4.xml: XML generated from the resultset in Figure 4-4.

<?xml version="1.0"?>
<ROOT>
  <row Dept_ID="1" Dept_Name="IT" Employee_ID="3" Employee_Name="MacDonald, Scott"/>
  <row Dept_ID="2" Dept_Name="HR" Employee_ID="2" Employee_Name="Jordan, Katie"/>
  <row Dept_ID="2" Dept_Name="HR" Employee_ID="4" Employee_Name="O'Brien, Tim"/>
  <row Dept_ID="2" Dept_Name="HR" Employee_ID="6" Employee_Name="Norman, Laura"/>
  <row Dept_ID="3" Dept_Name="Operations" Employee_ID="1" Employee_Name="Barr, Adam"/>
  <row Dept_ID="3" Dept_Name="HR" Employee_ID="5" Employee_Name="Koduri, Sunil"/>
  </row>
</ROOT>

Our multitable XML document has one significant weakness. It doesn't represent the true hierarchical nature of the resultset. Ideally, what we might want is to group Employee rows that have the same Dept_ID under the same container element. Listing 4-8 is an example.

Listing 4-8 sample_query5.xml: Using nested elements to represent a multitable resultset.

<?xml version="1.0"?>
<ROOT>
  <Dept Dept_ID="1" Dept_Name="IT">
    <Employee Employee_ID="3" Employee_Name="MacDonald, Scott"/>
  </Dept>
  <Dept Dept_ID="2" Dept_Name="HR">
    <Employee Employee_ID="2" Employee_Name="Jordan, Katie"/>
    <Employee Employee_ID="4" Employee_Name="O'Brien, Tim"/>
    <Employee Employee_ID="6" Employee_Name="Norman, Laura"/>
  </Dept>
  <Dept Dept_ID="3" Dept_Name="Operations">
    <Employee Employee_ID="1" Employee_Name="Barr, Adam"/>
    <Employee Employee_ID="5" Employee_Name="Koduri, Sunil"/>
  </Dept>
</ROOT>

Notice that we needed a new container element to hold data that is specific to a department (Dept). Instead of just using <row>, we decided to name this element according to the name of the table itself, <Dept>. Similarly, we've named the element used to hold data from each row of the Employee table as <Employee>.

To transform the resultset in Figure 4-4 into a nested XML document that looks like that shown in Listing 4-8, an RDBMS needs to navigate the rows in the resultset in a forward-only fashion and the columns in a left-to-right fashion. By comparing the column values of the current row with those of the previous rows, an RDBMS can infer the necessary hierarchical structure of the resultset. The nesting order is determined by the order in which the columns will appear in the resultset.

To better illustrate what's really involved, let's step through the process with the resultset shown in Figure 4-4. Before we begin using XML to mark up the data, we will first need to generate the header and root element, like the following:

<?xml version="1.0"?>
<ROOTSET>

Now let's start from the top of the resultset in Figure 4-4. Because this is the first row, however, we only need to "remember" the values here. We don't generate any XML at this point. We can move on to the second row.

Comparing the second row with the first row, we immediately find that the data has changed, so we need to generate elements in the XML. Because we see two groups of columns belonging to two different tables in these rows—Dept_ID and Dept_Name of the Dept table and Employee_ID and Employee_Name of the Employee table—we know we will need two separate elements to hold the column values. Furthermore, because the columns of the Dept table precede those of the Employee table, we will nest the element corresponding to the rows of the Employee table within those of the Dept table. The following XML fragment shows the result of this process:

  <Dept Dept_ID="1" Dept_Name="IT">
    <Employee Employee_ID="3" Employee_Name="MacDonald, Scott"/>

Here we have generated the elements and attributes for the previous row (row one) and not for the current row. This occurs because at this point we know only that the previous row is different from the current row. We don't yet know whether the current one will be different from the next row. Therefore, we need to generate the XML data for the previous row and remember the data in the current row. This is the general pattern we're going to follow until we reach the end of the resultset.

After moving on to the third row and comparing it with the previous row (row two), we find that both Dept_ID and Dept_Name stayed the same, but Employee_ID and Employee_Name have changed. This change means we need to add another <Employee> element to represent the Employee data in the previous row.

There is one slight complication. Because the column values of the row we're about to generate for both the Dept table and Employee table (that is, those of the second row) are different from those we have just generated (that is, those of the first row), we know we need to generate a new <Dept> element. However, because <Dept> is rendered as a container element, we also need to close the previous <Dept> element, as shown in the following code:

</Dept>
  <Dept Dept_ID="2" Dept_Name="HR">
    <Employee Employee_ID="2" Employee_Name="Jordan, Katie"/>

Now we will move on to the fourth row, where we find that, compared to the previous row, the column values of the Employee table have changed, which means we need to generate a new <Employee> element. The resulting XML that will be added looks like the following:

<Employee Employee_ID="4" Employee_Name="O'Brien, Tim"/>

Once we have this element, we move on to the fifth row where we encounter a situation similar to the second row. Therefore, we use the same solution and generate the necessary elements.

The XML result that we add to our growing document as follows here:

<Employee Employee_ID="6" Employee_Name="Norman, Laura"/>
  </Dept>
  <Dept Dept_ID="3" Dept_Name="Operations">

Now we hit the sixth row. First we find that the Employee column values have changed, so we generate a new <Employee> element, as shown here:

    <Employee Employee_ID="1" Employee_Name="Barr, Adam"/>

Finally we find that we've hit the end of the resultset. Therefore, the only task left is to spit out the elements for the last row and properly end our XML elements. We also know that we have a <Dept> element open, so we need to close it. Lastly we add a </ROOT> end tag to get our remaining elements. These steps are shown below:

    <Employee Employee_ID="5" Employee_Name="Koduri, Sunil"/>
  </Dept>
</ROOT>

You can see that the process is not that complicated and can easily be automated with some simple software.

Schema-Based Mapping

In this section we will show you how to generate database schemas to and from XSDs. Before we begin, however, you should realize that, because you can model an XSD in more than one way, the mapping method we are about to explore with you is not the only legitimate means of mapping XSD with database schemas. As you will learn soon, a few constructs in a database schema have no direct correspondence in XSD, and the solution we will use to solve this problem might be different from other people's similar attempts.

In a relational database the hierarchical relationship between tables can be modeled by using foreign keys. Therefore, in principle, we can model an XML representation of databases based on information found in the database schema. Take note, however, that this mapping method is most often used at design time to generate only an XML DTD or XSD schema. The reason for this is that, because most applications work with a known database schema, this method is not suitable for storing random XML documents in databases. That would require altering database schemas from XML at runtime.

This section makes heavy use of XSD. For a quick introduction to XSD, please read Appendix A. A more detailed coverage of the subject, "XML Schema Primer 0," can be found at http://www.w3.org/TR/xmlschema-0/ .

Generating XSD from a Database Schema

The exercise in this section is to generate an XSD schema that can correctly model both the database schemas shown in Listing 4-9. Note that Employee and Dept have a parent-child relationship because of the presence of the foreign key reference in the Employee table.

Listing 4-9 db_schema1.sql: Database schema for sample Dept and Employee tables.

CREATE TABLE Dept
(
  DeptID INT PRIMARY KEY NOT NULL,
  DeptName VARCHAR( 20 ) NOT NULL
)
CREATE TABLE Employee
(
  EmployeeID INT PRIMARY KEY NOT NULL,
  FirstName VARCHAR( 20 ),
  LastName VARCHAR( 20 ) NOT NULL,
  DeptID NUMBER REFERENCES Dept( Dept_ID ) NOT NULL
)

First we need to pick one of the two tables to generate an XSD model for it. Let's start with the Employee table. Now for each data column in the Employee table, namely FirstName and LastName, we need to generate a corresponding element in the XSD. Before we can do this, however, we need to define a new data type to model the VARCHAR( 20 ) data type definition that is used by both FirstName and LastName in our database schema. We can do this by extending the xsd:string data type to include this 20-character limit:

<xsd:simpleType name="varchar20">
  <xsd:restriction base="xsd:string">
<xsd:maxLength value="20"/>
  </xsd:restriction>
</xsd:simpleType>

We can now define the elements for FirstName and LastName as varchar20.

<xsd:element name="FirstName" type="varchar20"/>
<xsd:element name="LastName" use="required" type="varchar20"/>

Notice that, in addition to using our new varchar20 data type, we have also specified LastName as required because it is defined as not nullable in the database schema.

Next we tackle the primary key field of the Employee table, namely EmployeeID. First we model it as a unique and required element.

<xsd:element name="EmployeeID" use="required" type="xsd:int">
  <xsd:unique name="EmployeeUnique">
    <xsd:selector xpath="my:Employee"/>
    <xsd:field xpath="EmployeeID"/>
  </xsd:unique>
</xsd:element>

Notice that the uniqueness of the EmployeeID element is defined using the <xsd:unique> element. The <xsd:selector> element specifies that any value of EmployeeID must be unique within the <Employee> element. (We will include the definition for the my namespace URI used here when we get to the final steps of completing our XSD.) The <xsd:field> element associates this <xsd:unique> definition with the EmployeeID element.

Our model for the EmployeeID field has a problem, though. In a database schema a column can be defined as not primary key but still unique. Just saying that our EmployeeID is required is not enough; we need to be able to specify that EmployeeID is also a primary key.

It turns out that XSD has no direct notation to represent the concept of a primary key. One possible way to embed this extra information in our XSD is by using an <appinfo><annotation>. An <appinfo><annotation> is analogous to processing instructions in XML 1 because it is intended to be used for passing information to a processing application. Therefore, let's add our custom primary-key <appinfo><annotation> to our XSD.

<xsd:element name="EmployeeID" use="required" type="xsd:int">
  <xsd:unique name="EmployeeUnique">
 <xsd:selector xpath="my:Employee"/>
    <xsd:field xpath="EmployeeID"/>
      <xsd:annotation>
        <xsd:appinfo>
          primary-key
        </xsd:appinfo>
      </xsd:annotation>
    </xsd:unique>
  </xsd:element>

Here we have included the <annotation> element within the <unique> element so that a processor can easily associate this primary-key property with the correct element.

Besides primary key, many other attributes and metadata of databases cannot easily be modeled directly using the existing XSD constructs. One important example is all the information related to the definition of indexes. Of course, you can use the same <appinfo> <annotation> technique shown here for these purposes.

The only column left to model now is DeptID. In the Dept table, DeptID is defined as a foreign key. How do we represent this in our XSD? The XSD language has constructs that are perfect for this purpose, the <key> and <keyref> elements. Here <keyref> acts as a foreign key while <key> acts as a primary key. We can now model the DeptID field as follows:

<xsd:element name="DeptID" use="required" type="xsd:int">
  <xsd:keyref name="DeptKeyRef" refer="DeptKey">
    <xsd:selector xpath="my:Employee"/>
    <xsd:field xpath="DeptID"/>
  </xsd:keyref>
</xsd:element>

As you will notice, the syntax of <keyref> is very similar to <unique>, in that we use <selector> and <field> child elements to specify where we want the key relationship to be placed, and in what range it must be unique. The refer attribute is used to specify the name of the <key> element that we want to refer back to. When we model the DeptID field of the Dept table, we will name the associated <key> element as DeptKey.

Now that our XSD model for the Employee table is complete, we can move on to the Dept table. Modeling the DeptName field is easy because it is the same as the FirstName field of the Employee table:

<xsd:element name="DeptName" type="varchar20"/>

The DeptID field is defined as a primary key in the Dept table. We already know how to model a primary key from when we tackled the <EmployeeID> element, so we can quickly jump to the following definition of the <DeptID> element:

<xsd:element name="DeptID" use="required" type="xsd:int"/>
  <xsd:unique name="DeptUnique">
  <xsd:selector xpath="my:Dept"/>
  <xsd:field xpath="DeptID"/>
  <xsd:annotation>
    <xsd:appinfo>
      primary-key
    </xsd:appinfo>
  </xsd:annotation>
</xsd:unique>

However, one piece of information is still missing. We need to include a <key> element here so that our earlier <keyref> definition in the <DeptID> child element of the <Employee> element is complete. The final definition for the <DeptID> element is as follows:

<xsd:element name="DeptID" use="required" type="xsd:int"/>
  <xsd:unique name="DeptUnique">
    <xsd:selector xpath="my:Dept"/>
    <xsd:field xpath="DeptID"/>
    <xsd:annotation>
      <xsd:appinfo>
        primary-key
      </xsd:appinfo>
    </xsd:annotation>
  </xsd:unique>
  <xsd:key name="DeptKey">
    <xsd:selector xpath="my:Dept"/>
    <xsd:field xpath="DeptID"/>
  </xsd:key>
</xsd:element>

Now that we have all the pieces, we can tie them together into a complete XSD schema document. Our final completed XSD schema, including the XML header and definition for a root element, is shown in Listing 4-10.

Listing 4-10 employee_dept.xsd: XSD generated from the Employee and Dept schemas.

<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3c.org/2001/XMLSchema"
            targetNamespace="http://mspress.microsoft.com/corexml/"
            xmlns:my="http://mspress.microsoft.com/corexml/">
  <xsd:element name="ROOT">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element ref="Employee" minOccurs="0"
maxOccurs="unbounded"/>
        <xsd:element ref="Dept" minOccurs="0" maxOccurs="unbounded"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:simpleType name="varchar20">
    <xsd:restriction base="xsd:string">
  <xsd:maxLength value="20"/>
  </xsd:restriction>
  </xsd:simpleType>
  <xsd:element name="Employee">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="EmployeeID" use="required" type="xsd:int">
          <xsd:unique name="EmployeeUnique">
            <xsd:selector xpath="my:Employee"/>
            <xsd:field xpath="EmployeeID"/>
            <xsd:annotation>
              <xsd:appinfo>
                primary-key
              </xsd:appinfo>
            </xsd:annotation>
          </xsd:unique>
        </xsd:element>
        <xsd:element name="FirstName" type="varchar20"/>
        <xsd:element name="LastName" use="required" type="varchar20"/>
        <xsd:element name="DeptID" use="required" type="xsd:int">
          <xsd:keyref name="DeptKeyRef" refer="DeptKey">
           <xsd:selector xpath="my:Employee"/>
            <xsd:field xpath="DeptID"/>
          </xsd:unique>
        </xsd:element>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="Dept">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="DeptID" use="required" type="xsd:int"/>
          <xsd:unique name="DeptUnique">
            <xsd:selector xpath="my:Dept"/>
            <xsd:field xpath="DeptID"/>
            <xsd:annotation>
              <xsd:appinfo>
                primary-key
              </xsd:appinfo>
            </xsd:annotation>
          </xsd:unique>
          <xsd:key name="DeptKey">
            <xsd:selector xpath="my:Dept"/>
            <xsd:field xpath="DeptID"/>
          </xsd:key>
        </xsd:element>
        <xsd:element name="DeptName" type="varchar20"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

As you can see, the XSD language provides many features, such as the unique and key elements we showed you here, to enable accurate mapping to and from database schemas. For the actions that cannot be easily implemented directly in XSD, such as the primary-key property, we can use methods such as annotation to achieve a workable solution at least.

Generating a Database Schema from an XSD

In this section we will show you how to generate a database schema based on an XSD. For the examples here we will use the XSD schema in Listing 4-10 that we generated in the previous section. To better illustrate the mapping process, our database schema will be represented by an SQL creation script.

The first detail we need to decide on is what tables we're going to be mapping. For our case this is easy, because in Listing 4-10 we know that all these tables will be listed under the <ROOT> element. For each of the tables listed we generate a CREATE TABLE statement block for it in our SQL script.

CREATE TABLE Employee
(
)
CREATE TABLE Dept
(
)

Now we need to work through each of these tables to model their structures as XSD. Let's start with the Employee table because it's the first table listed in <ROOT>. In Listing 4-10 we know that every table is modeled as a <complexType> and that each column in a specific table is modeled as a child element. Armed with this knowledge, we find that the Employee table needs four columns.

Each of the columns in this table will have a corresponding child element defined under the <Employee><complexType>. In addition to their names, we also find other important information about these columns, such as whether they are required, unique, or both. We will need to generate the appropriate column definitions for each of these columns in our SQL script. Notice that the required attribute of the elements is represented in our SQL scripts using the NOT NULL standard SQL modifiers.

CREATE TABLE Employee
(
  EmployeeID INT NOT NULL,
  FirstName VARCHAR( 20 ),
  LastName VARCHAR( 20 ) NOT NULL,
  DeptID INT NOT NULL
)

Going back to our XSD in Employee_Dept.xsd, we find that EmployeeID has an associated unique element and a primary-key <appinfo><annotation>. As we've seen, the use of these two pieces of information represent our way of saying this element is a primary key column. Let's add this information to our SQL script:

  EmployeeID INT PRIMARY KEY NOT NULL,

Now comes the fun part: modeling the foreign key. We find from the definition of DeptID in the <Employee> element that it has a <keyref> to a <key> called DeptKey. Searching through Listing 4-10, we find that this DeptKey <keyref> is associated with the DeptID element within the <Dept><complexType> element. Now we have the information we need to model this foreign key relationship in our database schema.

  DeptID INT REFERENCES Dept( DeptID ) NOT NULL

This step completes our modeling for the Employee table, so now let's turn to the Dept table.

As before, first we add a column to our database schema for each element defined within the <Dept><complexType>.

CREATE TABLE Dept
(
  DeptID INT NOT NULL,
  Name VARCHAR( 20 ) NOT NULL
)

Now we find out that DeptID should be defined as a primary key because of the presence of the primary-key <appinfo><annotation>. Here we make the final change to our database schema, which yields Listing 4-11.

Listing 4-11 generated_db_schema.sql: DB Schema generated from the Employee_Dept.xsd XML document.

CREATE TABLE Employee
(
  EmployeeID INT PRIMARY KEY NOT NULL,
  FirstName VARCHAR( 20 ),
  LastName VARCHAR( 20 ) NOT NULL,
  DeptID INTEGER REFERENCES Dept(DeptID) NOT NULL
)
CREATE TABLE Dept
(
  DeptID INT PRIMARY KEY NOT NULL,
  Name VARCHAR( 20 ) NOT NULL
)

Storing and Retrieving Document-Centric XML Documents

Support for document-centric XML documents in most RDBMS products is usually minimal, if not totally missing. This absence results from the freeform and less organized nature of document-centric XML documents that makes them difficult to fit into the framework of a relational model. To illustrate this difficulty, consider Listing 4-12.

Listing 4-12 sample_html.html: An sample document-centric HTML document.

<html>
  <head>
    <title>Sample HTML</title>
  </head>
  <body>
    <h1>This is a header</h1>
    Some other text
    <p>This is a paragraph.</p>
    <p>This is <b>another</b> paragraph.</p>
  </body>
</html>

Note that although some elements look well structured, such as the <title> element, some mixed content is in the document, such as the <b> element within the <p> element. The presence of mixed content and deeply nested elements in the document makes it difficult to model properly using a relational model. For example, which elements do you model as tables and which as rows/columns? Also, is there a point to decomposing this document to fit into a relational model? What will you gain by doing that?

Most RDBMS products recommend that you store document-centric XML documents as Character Large Object (CLOB) or BLOB columns in a relational database. Doing so allows XML documents of arbitrary size to be easily stored in and retrieved from databases.

This concludes our discussion of using XML with RDBMS in general. In the following sections, we will explore the XML features provided by some commercial RDBMS products.


XML Support in Commercial Relational Database Management Systems

In the race to provide the "best" XML support, RDBMS vendors have developed tools to XML-enable their products. The level of support can vary greatly, though. Most RDBMSs support XML as an output format by using dynamic resultset-based mapping. Only a few RDBMS products, however, have built-in support for updating databases using XML. In this section we will explore the XML capabilities of two of the more popular RDBMSs in the market: Microsoft SQL Server 2000 and Oracle 9i.

Microsoft SQL Server 2000

SQL Server 2000 provides a host of built-in features for delivering XML support. Most of these concentrate on enabling the user to translate relational data into XML documents. In addition, SQL Server 2000 has a mechanism for updating databases using XML.

Using SELECT FOR XML to Expose Relational Data as XML

SQL Server 2000 features a new clause of the SELECT statement that can be used to expose existing relational data as XML instead of as standard resultsets. This capability makes it trivial to expose resultsets generated by any SQL queries as XML documents. It also eliminates the need for you to write any custom code to manually do the transformation from resultsets to XML.

The new FOR XML clause supports three modes—RAW, AUTO, and EXPLICIT—for controlling the shape of the resulting XML.

The RAW Mode

The RAW mode is the simplest of the three modes. Unfortunately, it is also the most limited. The RAW mode provides a simple one-to-one row mapping to XML.

Let's start with a simple example. The following query will return all rows in the Employees table:

SELECT EmployeeID, FirstName, LastName
  FROM Employees
The Employees table, all other tables, and their data mentioned in this section can be found in the Northwind sample database that is installed by default with SQL Server 2000.

The resultant resultset is shown in Figure 4-5.

Figure 4-5 The Employees resultset.

To return an XML you can add the FOR XML RAW clause to the SELECT query in Listing 4-13.

Listing 4-13 raw.sql: An XML-generating simple query.

SELECT EmployeeID, FirstName, LastName
  FROM Employees
  FOR XML RAW

With this addition, Listing 4-14 will be returned.

Listing 4-14 raw.xml: XML generated by the query in Listing 4-13.

<row EmployeeID="1" FirstName="Nancy" LastName="Davolio"/>
<row EmployeeID="2" FirstName="Andrew" LastName="Fuller"/>
<row EmployeeID="3" FirstName="Janet" LastName="Leverling"/>
<row EmployeeID="4" FirstName="Margaret" LastName="Peacock"/>
<row EmployeeID="5" FirstName="Steven" LastName="Buchanan"/>
<row EmployeeID="6" FirstName="Michael" LastName="Suyama"/>
<row EmployeeID="7" FirstName="Robert" LastName="King"/>
<row EmployeeID="8" FirstName="Laura" LastName="Callanan"/>
<row EmployeeID="9" FirstName="Annee" LastName="Dodsworth"/>

As shown in Listing 4-14, any XML document returned by FOR XML does not automatically include any prolog, such as <?xml version="1"?>, and a root element. We will show you one way to deal with this issue in a later section where we discuss the XML Templates feature of SQL Server 2000.

The RAW mode of querying transforms each row in the resultset generated by the query into an XML <row> element. Each column is rendered as an attribute of the same name. One of the biggest limitations of the RAW mode is that it cannot be used to generate a hierarchical XML document from a multitable resultset. To illustrate this limitation, let's use the multitable query shown in Listing 4-15 to generate a resultset.

Listing 4-15 multitable.sql: A multitable query.

SELECT Customers.CustomerID, Customers.CompanyName,
       Orders.OrderID, Orders.OrderDate,
       [Order Details].ProductID, [Order Details].Quantity,
 [Order Details].UnitPrice
  FROM Customers, Orders, [Order Details]
  WHERE Customers.CustomerID = Orders.CustomerID
    AND Orders.OrderID = [Order Details].OrderID
AND Customers.CustomerID = 'BOLID'
  ORDER BY Customers.CustomerID, Orders.OrderID

The resultant resultset of this query is shown in Figure 4-6.

Figure 4-6 The resultset generated by Listing 4-15.

Again, to generate XML, we add the FOR XML RAW to our query. The result is shown in Listing 4-16.

Listing 4-16 raw2. xml: XML generated by the RAW mode query in Listing 4-15.

<row CustomerID="BOLID" CompanyName="Bolido Comidas preparadas"
OrderID="10326" OrderDate="1996-10-10T00:00:00" ProductID="4"
Quantity="24" UnitPrice="17.6"/>
<row CustomerID="BOLID" CompanyName="Bolido Comidas preparadas"
OrderID="10326" OrderDate="1996-10-10T00:00:00" ProductID="57"
Quantity="16" UnitPrice="15.6"/>
<row CustomerID="BOLID" CompanyName="Bolido Comidas preparadas"
OrderID="10326" OrderDate="1996-10-10T00:00:00" ProductID="75"
Quantity="50" UnitPrice="6.2"/>
<row CustomerID="BOLID" CompanyName="Bolido Comidas preparadas"
OrderID="10801" OrderDate="1997-12-26T12:00:00" ProductID="17"
Quantity="40" UnitPrice="39"/>
<row CustomerID="BOLID" CompanyName="Bolido Comidas preparadas"
OrderID="10801" OrderDate="1997-12-26T12:00:00" ProductID="29"
Quantity="20" UnitPrice="123.79"/>
<row CustomerID="BOLID" CompanyName="Bolido Comidas preparadas"
OrderID="10970" OrderDate="1998-03-24T00:00:00" ProductID="52"
Quantity="40" UnitPrice="7"/>

As you can see, this does not reflect the hierarchy of our resultset Order Details under Orders under Customer. Luckily, the next mode we're going to discuss, the AUTO mode, is just as convenient to use but can also handle hierarchical data automatically.

The AUTO Mode

For a more configurable and powerful way to expose a resultset that is just as simple to use as the RAW mode, SQL Server 2000 provides the AUTO mode. The biggest improvement over the RAW mode is that the AUTO mode can be used to expose a multitable resultset as a hierarchical XML document. The query in Listing 4-17 is the same query shown in Listing 4-15 except that it now uses the AUTO mode.

Listing 4-17 auto.sql: SQL query to illustrate the AUTO mode.

SELECT Customers.CustomerID, Customers.CompanyName,
       Orders.OrderID, Orders.OrderDate,
       [Order Details].ProductID, [Order Details].Quantity,
 [Order Details].UnitPrice
  FROM Customers, Orders, [Order Details]
  WHERE Customers.CustomerID = Orders.CustomerID
    AND Orders.OrderID = [Order Details].OrderID
AND Customers.CustomerID = 'BOLID'
  ORDER BY Customers.CustomerID, Orders.OrderID
  FOR XML AUTO

The resultant XML is shown in Listing 4-18. As you will see, the AUTO mode has correctly rendered the hierarchical structure of our data in this XML.

Listing 4-18 auto.xml: XML generated using the query in AUTO.sql.

<Customers CustomerID="BOLID" CompanyName="Bolido Comidas preparadas">
  <Orders OrderID="10326" OrderDate="1996-10-10T00:00">
    <Order_Details ProductID="4" Quantity="24" UnitPrice="17.6"/>
    <Order_Details ProductID="57" Quantity="16" UnitPrice="15.6"/>
    <Order_Details ProductID="75" Quantity="50" UnitPrice="6.2"/>
  </Orders>
  <Orders OrderID="10801" OrderDate="1997-12-26T00:00">
    <Order_Details ProductID="17" Quantity="40" UnitPrice="39"/>
    <Order_Details ProductID="29" Quantity="20" UnitPrice="123.79"/>
  </Orders>
  <Orders OrderID="10801" OrderDate="1997-12-26T00:00">
    <Order_Details ProductID="52" Quantity="40" UnitPrice="7"/>
  </Orders>
</Customers>

In AUTO mode each table in the FROM clause is represented by a corresponding XML element. The columns listed in the query are mapped to the appropriate attribute of the element. The order in which column names are specified in the SELECT query is significant because it's used to control the hierarchy of the resultant XML. Therefore, in Listing 4-18, <Customers> contains the <Orders> elements which in turn contain the <Order_Details> elements because this is the order in which the corresponding tables are listed in the FROM clause.

The EXPLICIT Mode

The EXPLICIT mode of the FOR XML clause is designed to enable the user to control the shape of the XML explicitly. Using this mode, however, requires a steep learning curve. The query must be written so that the additional information about the expected nesting is explicitly specified as part of the query. SQL Server 2000 directives can be used to specify additional configurations information at the column level. For example, the user can specify whether a particular column should be represented as an element or attribute in the XML.

The EXPLICIT mode expects the resultset resulting from an SQL query to follow a specific format. This resultset, called the Universal Table, should contain not just the data represented within the XML, but also metadata describing how the XML should be structured. This information is obtained by requiring the SQL query author to include two extract columns in the Universal Table and to name the columns according to a special encoding convention.

The two extra columns required by the EXPLICIT mode must be named Tag and Parent.

  • Tag must be specified as the first column in the Universal Table and is used to indicate the tag number of the current element. Tag is an integer and is numbered starting at 1.
  • The second column in the Universal Table must be called Parent. It's used to specify the tag number of the parent element. Parent is also an integer. To specify that an element has no parent (that is, it's at the top level of the tree), use a value of 0 or NULL.

These two elements determine the parent-child hierarchy in the resultant XML tree. For example, the elements in Listing 4-19 will have the Tag and Parent values in Table 4-1.

Listing 4-19 skeletal1.xml: Skeletal XML used to illustrate the Tag and Parent values used in the EXPLICIT mode.

<Customers ...>
  ...
  <Orders ...>
    ...
<Details ...>
  ...
</Details>
...
  </Orders>
  ...
</Customers>
Table 4-1 Tag and Parents Values of the Elements in Skeletal1.xml
Element | Tag | Parent
Customers | 1 | 0
Orders | 2 | 1
Details | 3 | 2

After the Tag and Parent columns you will need to include one column for each of the data items in the XML. For example, the following XML fragment will need four columns because it has four data items: CustomerID, ContactName, OrderID, and OrderDate.

<Customers CustomerID="..." ContactName="...">
  ...
  <Orders OrderID="..." OrderDate="..."/>
  ...
</Customers>

These columns in the Universal Table must be named according to a specific convention. This encoding convention is used to specify metadata about the element or attribute that will represent the column. Every column must be named using the following convention:

ElementName!TagNumber!AttributeName![Directive]
  • ElementName represents the element name that the column belongs to.
  • TagNumber specifies the tag number of the element.
  • AttributeName specifies the attribute or element name within the XML.
  • Directive is optional and is used to specify how a column should be rendered in the XML. For example, a column is generated as an attribute in the XML by default. By specifying the "element" Directive, the column will be rendered as a contained element instead. Directive supports many other options, and we will see some of them later in this section.

For example, the two data items, CustomerID and ContactName, in the following XML fragment can be described by the column names, Customers!1!CustomerID and Customers!1!ContactName.

<Customers CustomerID="..." ContactName="...">
...

Each row in the Universal Table identifies an occurrence of an element in the resultant XML. Therefore, there will be as many rows in the Universal Table as the number of elements in the resultant XML. Now that you've learned the theory, it's time for some real actions! Let's build the Universal Table for the XML first shown in Listing 4-18.

We know of 10 element occurrences (one occurrence of <Customers> plus three occurrences of <Orders> plus six occurrences of <Order_Details>), so we will need 10 rows in the Universal Table. Looking at the XML, it should be easy to fill in the Tag and Parent values. These values are shown in Table 4-2.

Table 4-2 Tag and Parent Values
Tag | Parent
1 | 0
2 | 1
3 | 2
3 | 2
2 | 1
3 | 2
2 | 1
3 | 2
3 | 2
3 | 2

Next we define the other columns. From the XML we find seven data items (CustomerID, CompanyName, OrderID, OrderDate, ProductID, Quantity, and UnitPrice), so we need seven extra columns in the Universal Table to describe these items. This is shown in Table 4-3.

Table 4-3 Data Columns of the Universal Table for Generating AUTO.xml
Containing Element Name | Containing Element Tag Number | Item Name | Universal Table's Column Name
<Customers> | 1 | CustomerID | Customers!1!CustomerID
<Customersk> | 1 | ContactName | Customers!1!CompanyName
<Orders> | 2 | OrderID | Orders!2!OrderID
<Orders> | 2 | OrderDate | Orders!2!OrderDate
<Order_Details> | 3 | ProductID | Order_Details!3!ProductID
<Order_Details> | 3 | Quantity | Order_Details!3!Quantity
<Order_Details> | 3 | UnitPrice | Order_Details!3!UnitPrice

We now have the complete columns definition for our Universal Table. We also know how many rows we will have and their corresponding Tag and Parent values. What's left now is to put in the actual data, which you can see in Figure 4-7.

The first row corresponds to the first occurrence of the <Customers> element, and we need to store the values found in its CustomerID and ContactName attributes in the Customers!1!CustomerID and Customers!1!CompanyName columns, respectively. Note that we leave the rest of the columns blank (with a NULL value) because they don't have any values in the current element.

The second row corresponds to the first occurrence of the <Orders> element in the XML. This time we put the values stored in its OrderID and OrderDate attributes into the corresponding Orders!2!OrderID and Orders!2!OrderDate columns. We can leave the rest of the columns blank. Notice we have repeated the value for the CustomerID column from the previous row but left the CompanyName column blank. The reason for doing this will become clearer when we examine the SQL query needed to generate this Universal Table.

Figure 4-7 The completed Universal Table for generating the XML shown in Listing 4-18.

In the third row, we hit the first occurrence of the <Order_Details> element. Following the same rules described earlier, we fill this row out. Notice that this time we've copied the values for both the CustomerID and OrderID columns from the previous row while leaving the CompanyName and OrderDate blank. As promised, the reasons for this will be explained later in this section.

The fourth row is the same as the third row in terms of which columns it needs to complete; we just need to fill in the different column values. The fifth row is also similar to the third and fourth.

Now we have completed the first occurrence of <Order> and hit the second occurrence. We will use the same rules we employed in building the previous five rows in completing the second occurence here.

The Universal Table we will need for SQL Server 2000 to generate the appropriate XML in Listing 4-18 is now complete. However, given the table's relationship in Listing 4-16, we must use the proper SQL to generate a resultset like that shown in Figure 4-7. The UNION ALL statement is a perfect fit for achieving this effect. Listing 4-20 shows the SQL query required to generate the XML shown in Listing 4-18.

If we take a closer look at Listing 4-20, the first detail we notice is the three separate SQL queries joined by two UNION ALL statements. This corresponds to the number of distinct element types we have in AUTO.xml, namely <Customers>, <Orders>, and <Order-Details>.

Listing 4-20 explicit.sql: The EXPLICIT mode SQL query that can be used to generate the Universal Table shown in Figure 4-7.

SELECT 1 AS Tag,
       0 AS Parent,
       CustomerID AS [Customers!1!CustomerID],
       CompanyName AS [Customers!1!ContactName],
       NULL AS [Orders!2!OrderID],
       NULL AS [Orders!2!OrderDate],
       NULL AS [Order_Details!3!ProductID],
       NULL AS [Order_Details!3!Quantity],
       NULL AS [Order_Details!3!UnitPrice]
  FROM Customers
  WHERE CustomerID = 'BOLID'
UNION ALL
SELECT 2,
       1,
       CustomerID,
       NULL,
       Orders.OrderID,
       Orders.OrderDate,
       NULL,
       NULL,
       NULL
  FROM Orders, Customers
  WHERE Orders.CustomerID = Customers.CustomerID
    AND Customers.CustomerID = 'BOLID'
UNION ALL
  SELECT 3,
         2,
         CustomerID,
         NULL,
         OrderID,
         NULL,
         [Order Details].ProductID,
         [Order Details].Quantity,
         [Order Details].UnitPrice,
  FROM Orders, Customers, [Order Details]
  WHERE Orders.CustomerID =  Customers.CustomerID
    AND [Order Details].OrderID = Orders.OrderID
AND Customers.CustomerID = 'BOLID'
  ORDER BY Customers.CustomerID, Orders.OrderID
  FOR XML EXPLICIT

In the first query all the attribute values for the <Customers> element are obtained. We have given aliases to the columns according to the naming conventions required by the Universal Table. Notice that values for all the columns not related to the <Customers> element are set to NULL. Finally the values for the Tag and Parent columns are set to required values, in this case 1 and 0.

In the second query all the attribute values for the <Orders> element are retrieved. Notice that we've joined the Orders table to the Customers table, and we used the same criteria for selection of the Customers row as we used in the first query. This step ensures that the Customers we found in this query match those found in the first query. Also note that, except for CustomerID, all the columns not related to the <Orders> element are set to NULL. We've also assigned the values of 2 and 1 to the Tag and Parent columns.

In the final query we obtain all the attribute values for the <Order_Details> element. Notice that this time we have retained both the CustomerID and OrderID columns and left the other columns not related to the <Order_Details> element as NULL. We've also set up the same relationship between the Customers and Orders tables as the previous query to ensure that the rows returned in this query are consistent with those returned in the previous two queries.

In the EXPLICIT mode the resultset representing the Universal Table is scanned one row at a time in a forward-only manner, producing the resultant XML tree. To yield the proper XML hierarchy, use an ORDER BY clause in the query to ensure the correct order of the rows in the resultset.

The need for this ORDER BY clause also explains why we've repeated certain column values in the Universal Table but left others NULL. The proper order required by the EXPLICIT mode is ensured by having the values of the columns specified in the ORDER BY clause present in all the rows in the resultset.

It would be pointless to work through the complexity of using the EXPLICIT mode just so you can create XML documents that can be easily generated using the AUTO mode. The EXPLICIT mode is also designed to do much more. As an example, let's make a few modifications to the XML in Listing 4-18. This new XML is shown in Listing 4-21.

We've made some significant modifications in Listing 4-21. First we renamed all the elements; <Customers> became <Customer>, <Orders> became <Order>, and <Order_Details> became <Item>. Second we changed some of the data items into elements: CompanyName, OrderDate, UnitPrice, and Quantity. Generating an XML like this is impossible to achieve using the AUTO mode. With the EXPLICIT mode, however, it's almost trivial. Listing 4-22 shows the required query.

Listing 4-21 explicit.xml: An XML document based on AUTO.xml and customized with mixed attribute and element data values.

<Customer CustomerID="BOLID">
  <CompanyName>Bolido Comidas preparadas</CompanyName>
  <Order OrderID="10326">
    <OrderDate>1996-10-10T00:00</OrderDate>
<Item ProductID="4">
  <Quantity>24</Quantity>
  <UnitPrice>17.6</UnitPrice>
</Item>
<Item ProductID="57">
  <Quantity>16</Quantity>
  <UnitPrice>15.6</UnitPrice>
</Item>
<Item ProductID="75">
  <Quantity>50</Quantity>
  <UnitPrice>6.2</UnitPrice>
</Item>
  </Order>
  <Order OrderID="10801">
    <OrderDate>1997-12-26T00:00</OrderDate>
<Item ProductID="17">
  <Quantity>40</Quantity>
  <UnitPrice>39</UnitPrice>
</Item>
<Item ProductID="29">
  <Quantity>20</Quantity>
  <UnitPrice>123.79</UnitPrice>
</Item>
  </Order>
  <Order OrderID="10970">
    <OrderDate>1998-03-24T00:00</OrderDate>
<Item ProductID="52">
  <Quantity>40</Quantity>
  <UnitPrice>7</UnitPrice>
</Item>
  </Order>
</Customer>

Listing 4-22 explicit2.sql: An SQL query that can be used to generate the XML in EXPLICIT.xml.

SELECT 1 AS Tag,
       0 AS Parent,
       CustomerID AS [Customer!1!CustomerID],
       ContactName AS [Customer!1!CompanyName!element],
       NULL AS [Order!2!OrderID],
       NULL AS [Order!2!OrderDate!element],
       NULL AS [Item!3!ProductID],
       NULL AS [Item!3!Quantity!element],
       NULL AS [Item!3!UnitPrice!element]
  FROM Customers
  WHERE CustomerID = 'BOLID'
 UNION ALL
SELECT 2,
       1,
       CustomerID,
       NULL,
       Orders.OrderID,
       Orders.OrderDate,
       NULL,
       NULL,
       NULL
  FROM Orders, Customers
  WHERE Orders.CustomerID = Customers.CustomerID
    AND Customers.CustomerID = 'BOLID'
UNION ALL
  SELECT 3,
         2,
         CustomerID,
         NULL,
         OrderID,
         NULL,
         [Order Details].ProductID,
         [Order Details].Quantity,
         [Order Details].UnitPrice,
  FROM Orders, Customers, [Order Details]
  WHERE Orders.CustomerID =  Customers.CustomerID
    AND [Order Details].OrderID = Orders.OrderID
AND Customers.CustomerID = 'BOLID'
  ORDER BY Customers.CustomerID, Orders.OrderID
  FOR XML EXPLICIT

Notice that all the changes required are isolated to the first query, and all we had to do was specify different aliases for the columns affected. Specifically, all the elements were renamed accordingly (Customer, Order, and Item). In addition, we included the element directive for those columns that we wanted to render as elements instead of attributes.

Many other options are available to tweak the behavior of the EXPLICIT mode. We have shown you the basics in this section, and if you're interested we encourage you to consult your SQL Server 2000 documentation for further details.

XML Templates

SQL Server 2000 provides a new feature, called XML Templates, that can be used to wrap multiple FOR XML queries or stored procedures into a valid XML document. XML Templates also support advanced features such as runtime parameter substitution and support for XSLT of the resulting XML document.

An XML Template is an XML document that contains the query strings or stored procedures to execute. Listing 4-23 shows how you can wrap a RAW mode query that returns all rows from the Employees table within an XML Template.

Listing 4-23 xml_template.xml: A simple XML Template.

<?xml version="1.0"?>
<ROOT xmlns:sql="urn:schemas-microsoft-com:xml-sql">
  <sql:query>
    SELECT EmployeeID, FirstName, LastName
      FROM Employees
      FOR XML RAW
  </sql:query>
</ROOT>

In Listing 4-23 the <ROOT> element specifies the root element of the resulting XML document. You can, of course, call it anything you want, such as <Customers>. This root element serves to make the resulting XML document valid. Notice that we've also included the XML Template namespace definition in the <ROOT> element. The SQL Server 2000 XML Template XSD is identified by a URI of urn:schemas-microsoft-com:xml-sql.

As shown in Listing 4-23, you use the <query> element to specify a query or stored procedure to execute. You can have as many <query> elements as you like, and the results of all the queries are included together in the final XML document.

Now we need to actually execute our XML Template to retrieve the resulting XML document. One of the methods SQL Server 2000 provides for achieving this is using an HTTP URL. This feature requires you to set up a virtual directory and a virtual name of template type in Microsoft Internet Information Server (IIS) by using the IIS utility, Virtual Directory Management for SQL Server 2000. For instructions on configuring IIS this way, please consult your SQL Server 2000 documentation. Once IIS is configured properly, you can execute our XML Template using a URL similar to the following:

http://localhost/nwind/templates/XML_Template1.xml

In the above URL nwind and templates are examples of the virtual directory and virtual name, respectively. Listing 4-24 shows the resulting XML document when this URL is opened in a browser.

Listing 4-24 xml_template_result.xml: XML document generated from an XML Template.

<?xml version="1.0"?>
<ROOT xmlns:sql="urn:schemas-microsoft-com:xml-sql">
  <row EmployeeID="1" FirstName="Nancy" LastName="Davolio"/>
  <row EmployeeID="2" FirstName="Andrew" LastName="Fuller"/>
  <row EmployeeID="3" FirstName="Janet" LastName="Leverling"/>
  <row EmployeeID="4" FirstName="Margaret" LastName="Peacock"/>
  <row EmployeeID="5" FirstName="Steven" LastName="Buchanan"/>
  <row EmployeeID="6" FirstName="Michael" LastName="Suyama"/>
  <row EmployeeID="7" FirstName="Robert" LastName="King"/>
  <row EmployeeID="8" FirstName="Laura" LastName="Callanan"/>
  <row EmployeeID="9" FirstName="Annee" LastName="Dodsworth"/>
</ROOT>

As shown, SQL Server 2000 executed our RAW mode query and inserted the result within the <ROOT> element.

XML View

For users who need to be able to shape the XML extensively but find using the EXPLICIT mode too difficult, SQL Server 2000 provides a new feature referred to as XML View. XML View is basically an XML-to-relational database mapping mechanism that allows the user to fine-tune the shape of the XML. In addition, queries against XML Views are made by using a subset of the XPath language. This approach is similar to creating SQL views using CREATE VIEW statements and then querying against these views using SELECT statements.

In XML, View XML-to-relational database mapping is specified through a number of Microsoft-defined annotations to the XSD schema language. These annotations allow the user to specify the mapping between elements and attributes in the XSD schema to tables and columns in the databases. They are also used to specify the hierarchical relationships in the XML. For example, suppose we want to use the same tables and relationships as those used in generating the XML shown in Listing 4-18, but want a resultant XML that looks like that shown in Listing 4-25.

Listing 4-25 xml_view.xml: Another customized version of Listing 4-18 used to illustrate the capability of the XML View feature.

<Customer CustomerID="BOLID">
  <Name>Bolido Comidas preparadas</Name>
  <Order OrderID="10326">
    <Date>1996-10-10T00:00</Date>
<Item>
  <ProductID>4</ProductID>
  <Quantity>24</Quantity>
  <Price>17.6</Price>
</Item>
<Item>
  <ProductID>57</ProductID>
  <Quantity>16</Quantity>
  <Price>15.6</Price>
</Item>
<Item>
  <ProductID>75</ProductID>
  <Quantity>50</Quantity>
  <Price>6.2</Price>
</Item>
  </Order>
  <Order OrderID="10801">
    <Date>1997-12-26T00:00</Date>
<Item>
  <ProductID>17</ProductID>
  <Quantity>40</Quantity>
  <Price>39</Price>
</Item>
<Item>
  <ProductID>29</ProductID>
  <Quantity>20</Quantity>
  <Price>123.79</Price>
</Item>
  </Order>
  <Order OrderID="10970">
    <Date>1998-03-24T00:00</Date>
<Item>
  <ProductID>52</ProductID>
  <Quantity>40</Quantity>
  <Price>7</Price>
</Item>
  </Order>
</Customer>

To be able to generate an XML document like this, SQL Server 2000 requires that we specify an XSD with the appropriate mapping annotations. This special XSD is known as a mapping schema. The annotations are used to specify which XML elements correspond to which columns in a database table. They also specify the parent-child relationships between the tables.

The XSD schema for our example is shown here in Listing 4-26. Notice that all annotations defined in a mapping schema belong to the SQL Server 2000 XML View namespace, namely urn:schemas-microsoft-com:mapping-schema. In Listing 4-24 this namespace is mapped to the sql prefix.

Listing 4-26 xml_view.xsd: Mapping schema for the XML in Listing 4-25.

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:sql="urn:schemas-microsoft-com:mapping-schema">
  <xsd:element name="Customer" sql:relation="Customers">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Name" sql:field="CompanyName" type="xsd:string" />
        <xsd:element ref="Orders" sql:relationship="relOrders"/>
      </xsd:sequence>
      <xsd:attribute name="CustomerID" sql:field="CustomerID" type="xsd:string"/>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="Order" sql:relation="Orders">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Date" sql:field="OrderDate" type="xsd:dateTime"/>
        <xsd:element ref="Item" sql:relationship="relItems"/>
      </xsd:sequence>
      <xsd:attribute name="OrderID" type="xsd:int"/>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="Item" sql:relation="Order Details">
    <xsd:complexType>
      <xsd:element name="ProductID" sql:field="ProductID" type="xsd:string"/>
      <xsd:element name="Price" sql:field="UnitPrice" type="xsd:int"/>
      <xsd:element name="Quantity" sql:field="Quantity" type="xsd:int"/>
    </xsd:complexType>
  </xsd:element>
  <xsd:annotation>
    <xsd:appinfo>
      <sql:relationship name="relOrders"
                        parent="Customers"
                        parent-key="CustomerID"
                        child="Orders"
                        child-key="CustomerID"/>
      <sql:relationship name="relDetails"
                        parent="Orders"
                        parent-key="OrderID"
                        child="Order Details"
                        child-key="OrderID"/>
    </xsd:appinfo>
  </xsd:annotation>
</xsd:schema>

To specify the mapping between elements in the XML and columns in a database table, you use the sql:field attribute. The table where these columns can be found is specified using the sql:relation attribute. For example, the following fragment from Listing 4-26 defines the complex element <Item> as having subelements <ProductID>, <Price>, and <Quantity> mapped to the ProductID, UnitPrice, and the Quantity fields of the Details table respectively:

  <xsd:element name="Item" sql:relation="Order Details">
    <xsd:complexType>
      <xsd:element name="ProductID" sql:field="ProductID" type="xsd:string"/>
      <xsd:element name="Price" sql:field="UnitPrice" type="xsd:int"/>
      <xsd:element name="Quantity" sql:field="Quantity" type="xsd:int"/>
    </xsd:complexType>
  </xsd:element>

In addition to the column and table mapping specifications, we said earlier that XML View also allows you to specify the hierarchical shape of the XML. This is specified using the <sql:relationship> element. You need to use this annotation in two places.

First you need to specify how tables/views are to be joined. These join relationships are defined inside an annotation section using the first form of <sql:relationship>. The following fragment shows one of the join relationships we have defined in Listing 4-26:

<sql:relationship name="relOrders"
                  parent="Customers"
                  parent-key="CustomerID"
                  child="Orders"
                  child-key="CustomerID"/>

Each join relationship, or table join, is defined by an <sql:relationship> element. A name attribute is used to uniquely identify this relationship so that it can be referenced elsewhere in the XSD. Which tables and columns are involved in the join are defined by the four self-explanatory attributes parent, parent-key, child, and child-key. The previous fragment means that we are joining the Orders table to the Customers table using the CustomerID column of each of these tables as the keys. This is functionally equivalent to the following WHERE clause:

WHERE Customers.CustomerID = Orders.CustomerID

Now that you have the join relationships defined, you can use them to specify corresponding elements to be included in the resultant XML by defining an element that references one of these join relationships. Here the sql:relationship attribute is used to specify the name of the join relationship defined in the annotation section:

  <xsd:element ref="Item" sql:relationship="relItems"/>

Now that we have completed the design of our schema, how do we ask SQL Server 2000 to create an XML document using this schema? The answer is that SQL Server 2000 features a subset of the XPath language for querying against XML Views. For example, to perform the equivalent of the SELECT WHERE Customers.CustomerID = `BOLID' that we've used in our earlier examples, we can use the following XPath, which will return an XML like that shown in XML_view.xml:

/Customer[@CustomerID='BOLID']

You don't have to stop there. You can restrict the XML document to return only the list of the <Item> elements without the <Customer> and <Order> elements. Here is the XPath to achieve this:

/Customer[@CustomerID='BOLID'/Order/Item]

Using this string will return an XML fragment similar to the following:

<Item>
  <ProductID>918234</ProductID>
  <Price>12.00</Price>
  <Quantity>4</Quantity>
</Item>
<Item>
  <ProductID>170018</ProductID>
  <Price>20.00</Price>
  <Quantity>2</Quantity>
</Item>
<Item>
  <ProductID>100144</ProductID>
  <UnitPrice>49.95</UnitPrice>
  <Quantity>10</Quantity>
</Item>
<Item>
  <ProductID>710050</ProductID>
  <UnitPrice>199.95</UnitPrice>
  <Quantity>1</Quantity>
</Item>
<Item>
  <ProductID>918234</ProductID>
  <UnitPrice>12.00</UnitPrice>
  <Quantity>10</Quantity>
</Item>
<Item>
  <ProductID>001460</ProductID>
  <UnitPrice>299.95</UnitPrice>
  <Quantity>1</Quantity>
</Item>

Persisting XML Data Using OPENXML

SQL Server 2000 also features a resultset provider, called OPENXML, that provides a resultset view over an XML document. This feature can be useful when you want to send an XML document as a parameter to an SQL statement or stored procedure and then insert the data found in the document into the appropriate relational tables.

Functionally, OPENXML is similar to other resultset providers such as OPENQUERY and OPENROWSET. The major difference is that, with OPENXML, you need to use a special stored procedure to prepare your XML document and clear it from memory with another stored procedure after you're done.

To prepare your XML document, you call the sp_xml_prepareDocument stored procedure. This loads the XML document, passed in through the string parameter, into memory, preparses it for optimal query performance, and turns it into a resultset. The procedure, sp_xml_prepareDocument, returns an INT value, which represents a handle to the XML resultset in memory. You use this handle when you want to use the associated XML resultset. When you're done using this resultset, pass the handle to the sp_xml_removeDocument stored procedure to clear up the used memory.

The SQL script in Listing 4-27 shows how you can use OPENXML to insert data defined in a specific XML document into the Employees table.

You can see that the sp_xml_prepareDocument stored procedure takes two parameters. The first is an output parameter and is the returned handle value. The second is the XML document represented as a string. Depending on the size of the XML string, this input parameter can be CHAR, TEXT, or VARCHAR. You can also use the Unicode-ready version of these data types (NCHAR, NTEXT, and NVARCHAR).

Listing 4-27 openxml.sql: Inserting records using OPENXML.

DECLARE @xmlHnd INT
DECLARE @xmlDoc VARCHAR( 1000 )
-- prepare the XML document
SET @xmlDoc =
'<ROOT>
  <Employees EmployeeID="2" FirstName="Albert" LastName="Adams"/>
  <Employees EmployeeID="4" FirstName="Ed" LastName="Davis"/>
  <Employees EmployeeID="10" FirstName="Joe" LastName="Young"/>
</ROOT>'
-- Load and parse the XML document
EXEC sp_xml_prepareDocument @xmlHnd OUTPUT, @xmlDoc
-- Insert the three Employees record specified in the XML INSERT Employees
  SELECT *
    FROM OPENXML( @xmlHnd, '/ROOT/Employees', 0 )
           WITH ( EmployeeID INT, FirstName VARCHAR( 20 ), LastName VARCHAR( 20 ) )
-- release reference to the XML document
EXEC sp_xml_removeDocument @hXmlDoc

To turn the XML document identified by the handle that is returned by sp_xml_prepareDocument into a resultset, you use the OPENXML resultset function. OPENXML has the following syntax:

OPENXML( Handle, XPath[, Attributes] ) WITH SchemaDeclaration
  • Handle is the handle value returned by the sp_xml_prepareDocument stored procedure.
  • XPath is an XPath expression used to identify the nodes in the XML document that should be included in the resultset. Each node identified by the XPath expression corresponds to a single row in the resultset generated by OPENXML.
  • Attributes are optional and are used to specify the type of mapping (attribute-centric or element-centric) between the resultset columns and the XML nodes identified by the XPath expression. Attributes are integer values and the default value of 0 uses an attribute-centric mapping. A value of 2 identifies element-centric mapping.
  • SchemaDeclaration identifies the database schema declaration that OPENXML uses to generate the resultset. SchemaDeclaration uses a syntax similar to that used by the CREATE TABLE statement, namely ColumnName1 ColumnType1, ColumName2, ColumnType2, and so forth. Alternatively, you can specify the name of a table for SchemaDeclarations if this table already exists with the desired schema.

In our previous openxml.sql example our OPENXML call looks like the following:

OPENXML( @xmlHnd, '/ROOT/Employees', 0 ) WITH Employees

This code specifies that our XML document, identified by the xmlHnd handle, is attribute-centric by passing a value of 0 to the Attributes parameter (the third parameter). Next the XPath identifies that we intend to use the nodes found under the /ROOT/Employees element to generate the rows in the OPENXML resultset. Finally the WITH clause specifies the schema that will be used to construct the resultset.

Oracle 9i

SQL Server 2000 is not the only database that supports a wealth of XML integration. Oracle 9i also provides a number of powerful tools for building XML applications. However, compared to SQL Server 2000, these features are more complicated and usually require more programming.

Most of the XML features provided by Oracle 9i can be found in the Oracle XML Development Kits (XDK). Oracle makes XDKs for Java, C/C++, and PL/SQL. Components supplied in these XDKs include XML parsers, XLST processors, and other tools. One of these tools is the XML SQL Utility (XSU), which is used to generate XML documents from SQL queries.

Oracle 9i also provides a new data type, called XMLType, that can be used to natively persist both data-centric and document-centric XML documents in the database. This feature simplifies the task of using the database as an XML repository. Oracle 9i supports indexing the XML documents stored using XMLType through the Oracle Text package. Once indexed, these XML documents can be searched as text or as document sections.

In the code examples in the rest of this section we will use the tables and objects created and populated using the oracle_script.sql SQL script and displayed in Listing 4-28. If you are running these examples, use this script to create and populate your tables.

Listing 4-28 oracle_script.sql: A creation script for sample tables.

-- create and populate the Dept table
CREATE TABLE Dept
(
  Dept_No NUMBER PRIMARY KEY
  Dept_Name VARCHAR2( 20 ) NOT NULL
);
/
INSERT INTO Dept VALUES ( 1, `IT' );
/
INSERT INTO Dept VALUES ( 2, `Marketing' );
/
INSERT INTO Dept VALUES ( 3, `HR' );
/
INSERT INTO Dept VALUES ( 4, `Accounting' );
/
INSERT INTO Dept VALUES ( 5, `Operations' );
/
-- create and populate the Employee table
CREATE TABLE Employee
(
  Employee_No NUMBER PRIMARY KEY,
  First_Name VARCHAR2( 20 ),
  Last_Name VARCHAR2( 20 ) NOT NULL,
  Dept_No NUMBER REFERENCES Dept( Dept_No )
);
/
INSERT INTO Employee VALUES ( 1, 'Sean', 'Chai', 1 )
/
INSERT INTO Employee VALUES ( 2, 'Aaron', 'Con', 1 )
/
INSERT INTO Employee VALUES ( 3, 'Andrew', 'Dixon', 2 )
/
INSERT INTO Employee VALUES ( 4, 'Ted', 'Bremer', 3 )
/
INSERT INTO Employee VALUES ( 5, 'Randall', 'Boseman', 4 )
/
INSERT INTO Employee VALUES ( 6, 'Jane', 'Clayton', 1 )
/
INSERT INTO Employee VALUES ( 7, 'Peter', 'Connelly', 2 )
/
INSERT INTO Employee VALUES ( 8, 'Eva', 'Corets', 3 )
/
INSERT INTO Employee VALUES ( 9, 'Nate', 'Sun', 5 )
/

The XML SQL Utility

XSU is supplied as part of the Oracle XDK and is designed to enable the user to perform the following three tasks:

  • Generate an XML document from any SQL queries or Java JDBC Resultset objects.
  • Extract the data from an XML document and insert the data into the appropriate columns/attributes of a table or view.
  • Extract the data from an XML document and use this data to update or delete values of the appropriate columns/attributes or both.

The various XSU functionalities are accessible through a Java API, a PL/SQL API, or a command-line utility. For the examples in this section we will be using the Java API to illustrate using XSU. XSU uses a set of customizable resultset-based mapping rules, called SQL-XML mapping, to go from SQL to XML and vice versa. Let's take a look at what the default mapping does. Submit the following SQL query to XSU:

SELECT Employee_ID, First_Name, Last_Name
  FROM Employee
  ORDER BY Employee_ID

This simple SQL query generates Listing 4-29.

Listing 4-29 xsu_generated_xml1.xml: XML generated by XSU using default mapping.

<?xml version="1.0"?>
<ROWSET>
  <ROW num="1">
    <EMPLOYEE_ID>1</EMPLOYEE_ID>
    <FIRST_NAME>Sean</FIRST_NAME>
    <LAST_NAME>Chai</LAST_NAME>
  </ROW>
  <ROW num="2">
    <EMPLOYEE_ID>2</EMPLOYEE_ID>
    <FIRST_NAME>Aaron</FIRST_NAME>
    <LAST_NAME>Con</LAST_NAME>
  </ROW>
  <ROW num="3">
    <EMPLOYEE_ID>3</EMPLOYEE_ID>
    <FIRST_NAME>Andrew</FIRST_NAME>
    <LAST_NAME>Dixon</LAST_NAME>
  </ROW>
  <ROW num="4">
    <EMPLOYEE_ID>4</EMPLOYEE_ID>
    <FIRST_NAME>Ted</FIRST_NAME>
    <LAST_NAME>Bremer</LAST_NAME>
  </ROW>
  <ROW num="5">
    <EMPLOYEE_ID>5</EMPLOYEE_ID>
    <FIRST_NAME>Randall</FIRST_NAME>
    <LAST_NAME>Boseman</LAST_NAME>
  </ROW>
  <ROW num="6">
    <EMPLOYEE_ID>6</EMPLOYEE_ID>
    <FIRST_NAME>Jane</FIRST_NAME>
    <LAST_NAME>Clayton</LAST_NAME>
  </ROW>
  <ROW num="7">
    <EMPLOYEE_ID>7</EMPLOYEE_ID>
    <FIRST_NAME>Peter</FIRST_NAME>
    <LAST_NAME>Connelly</LAST_NAME>
  </ROW>
  <ROW num="8">
    <EMPLOYEE_ID>8</EMPLOYEE_ID>
    <FIRST_NAME>Eva</FIRST_NAME>
    <LAST_NAME>Cosets</LAST_NAME>
  </ROW>
  <ROW num="9">
    <EMPLOYEE_ID>9</EMPLOYEE_ID>
    <FIRST_NAME>Nate</FIRST_NAME>
    <LAST_NAME>Sun</LAST_NAME>
  </ROW>
</ROWSET>

With the default mapping, the root element is called <ROWSET>. <ROWSET> contains one or more <ROW> child elements, each of which is used to represent a row in the resultset. <ROW> has a single attribute, called num and is used to indicated the logical row number for the associated row. Finally each <ROW> element contains one or more child elements whose names and contents represent the columns in the resultset.

Generating XML Documents

To generate XML documents from resultsets you will need to write some Java code. All the functionality related to generating XML documents is provided by the oracle.xml.sql.query.OracleXMLQuery Java class. The general steps required to generate an XML document using XSU are

  1. Get a connection to the required Oracle database by using the Oracle JDBC Driver. You can use any Oracle-supported JDBC Driver such as the OCI8, Thin, or the server-side internal JDBC Driver.
  2. Create an instance of the OracleXMLQuery class and passing the constructor the SQL query in the form of a java.lang.String, a java.sql.ResultSet, or an oracle.xml.sql.dataset.OracleXMLDataSet.
  3. Specify optional features such as a different resultset, row name or both, defining the format for dates, or even specifying a style sheet header.
  4. Retrieve the generated XML document. You can get a string or DOM representation of this XML document by calling the getDOM ( ) or getXML( ) method, respectively.
  5. Release resources held by the OracleXMLQuery by calling its close( ) method.

Listing 4-30 shows a simple example of a Java console application that uses XSU to generate an XML document from a query.

Listing 4-30 xsu_select.java: Using XSU to generate an XML document from an SQL query.

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.xml.sql.query.*;
/* class to test the String generation! */
public class XSU_Select
{
  public static void main( String argv[] ) throws Exception
  {
    /* connect to Oracle */
    DriverManager.registerDriver( new oracle.jdbc.driver.
OracleDriver() );
    Connection conn = DriverManager.getConnection(
"jdbc:oracle:oci8:@", "scott", "tiger" );
    /* setup the SQL string */
    String sqlStr = "SELECT Last_Name || ', ' || First_Name
Employee_Name, Dept_Name" +
                    "  FROM Employee, Dept" +
                    "  WHERE Employee.Dept_No = Dept.Dept_No" +
                    "  ORDER BY Employee_ID";
    /* initiate an OracleXMLQuery with our SQL string */
    OracleXMLQuery qry = new OracleXMLQuery( conn, sqlStr );
    /* get the generated XML string */
    String xmlStr = qry.getXMLString();
    /* output the XML string to the console */
    System.out.println( xmlStr );
    /* always remember to close the OracleXMLQuery object after use
*/
    qry.close();
  }
}

Listing 4-30 first sets up a JDBC Connection to Oracle using the Oracle OCI8 JDBC driver (oracle.jdbc.driver.OracleDriver).

Listing 4-30 connects to the default database (identified by the ORA_SID environment variable) as user scott. To test this program in your environment, be certain that you make the necessary modifications to properly log in first.

Next an instance of the OracleXMLQuery class is created, passing it the JDBC Connection object and the SQL query string. Then xsu_select.java then calls the getXMLString( ) method of OracleXMLQuery to generate and return the XML document as a java.lang.String. This XML document is then displayed on the console. Finally Listing 4-30 releases the memory occupied by the instance of OracleXMLQuery.

To compile, first make sure you have the necessary CLASSPATH specified. Execute the command javac xsu_select.java. Once it is successfully compiled, execute XSU_select by entering java xsu_select.

Listing 4-31 is an example of the output that will be generated.

Listing 4-31 xsu_select.xml: XML generated by Listing 4-30.

<?xml version="1.0"?>
<ROWSET>
  <ROW num="1">
    <EMPLOYEE_ID>1</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Chai, Sean</EMPLOYEE_NAME>
    <DEPT_NAME>IT</DEPT_NAME>
  </ROW>
  <ROW num="2">
    <EMPLOYEE_ID>2</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Con, Aaron</EMPLOYEE_NAME>
    <DEPT_NAME>IT</DEPT_NAME>
  </ROW>
  <ROW num="3">
    <EMPLOYEE_ID>3</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Dixon, Andrew</EMPLOYEE_NAME>
    <DEPT_NAME>Marketing</DEPT_NAME>
  </ROW>
  <ROW num="4">
    <EMPLOYEE_ID>4</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Bremer, Ted</EMPLOYEE_NAME>
    <DEPT_NAME>HR</DEPT_NAME>
  </ROW>
  <ROW num="5">
    <EMPLOYEE_ID>5</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Boseman, Randall</EMPLOYEE_NAME>
    <DEPT_NAME>Accounting</DEPT_NAME>
  </ROW>
  <ROW num="6">
    <EMPLOYEE_ID>6</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Clayton, Jane</EMPLOYEE_NAME>
    <DEPT_NAME>IT</DEPT_NAME>
  </ROW>
  <ROW num="7">
    <EMPLOYEE_ID>7</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Connelly, Peter</EMPLOYEE_NAME>
    <DEPT_NAME>Marketing</DEPT_NAME>
  </ROW>
  <ROW num="8">
    <EMPLOYEE_ID>8</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Corets, Eva</EMPLOYEE_NAME>
    <DEPT_NAME>HR</DEPT_NAME>
  </ROW>
  <ROW num="9">
    <EMPLOYEE_ID>9</EMPLOYEE_ID>
    <EMPLOYEE_NAME>Sun, Nate</EMPLOYEE_NAME>
    <DEPT_NAME>Operations</DEPT_NAME>
  </ROW>
</ROWSET>

Storing XML Data

In addition to transforming a resultset into XML, XSU can also be used to insert XML data into tables, update existing tables with the data extracted from XML documents, and delete rows from tables based on element values stored in the XML documents. These are all provided by methods found in the oracle.xml.sql.dml.OracleXMLSave class. Using OracleXMLSave generally involves following these basic steps:

  • Get a connection to the required Oracle database by using the Oracle JDBC Driver. You can use any Oracle-supported JDBC Driver, such as the OCI8, Thin, or the server-side internal JDBC Driver.
  • Create an instance of the OracleXMLSave class and passing the constructor the name of the table or view on which insertions, updating, or deletions need to be done.
  • For insertion you have the option to specify a list of column names that you want the insert to work on. To do this you construct an array of java.lang.String containing the column names and then pass it to the setUpdateColumnList( ) method.
  • For updating you need to specify the list of key column names that will be used to select the rows for updating. You do this by building an array of java.lang.String containing the key column names and passing it to the setKeyColumnList( ) method. You can also specify the specific columns that you want to update instead of updating all the columns. To do this you construct an array of java.lang.String containing the column names and then pass it to the setUpdateColumnList( ) method.
  • For deletion you can specify the list of key column names that will be used to select the rows for deletion. You do this by building an array of java.lang.String containing the key column names and passing it to the setKeyColumnList( ) method.
  • Construct an XML document as a java.lang.String, java.io.InputStream, java.io.Reader, or as a DOM object.
  • For insertion invoke the insertXML( ) method and pass it the XML document constructed in the previous step.
  • For updating invoke the updateXML( ) method and pass it the XML document constructed in the previous step.
  • For deletion invoke the deleteXML( ) method and pass it the XML document constructed in the previous step.
  • Release resources held by the OracleXMLSave by calling its close( ) method.

Listing 4-32 is an example Java program that illustrates the insertion process.

Listing 4-32 xsu_insert.java: Inserting XML data into a table.

import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.xml.sql.dml.*;
public class XSU_Insert
{
  public static void main( String argv[] ) throws Exception
  {
    /* connect to Oracle */
    DriverManager.registerDriver( new oracle.jdbc.driver.
OracleDriver() );
    Connection conn = DriverManager.getConnection( "jdbc:
oracle:oci8:@", "scott", "tiger" );
    /* instantiate the OracleXMLSave object and specify that
       we will be inserting into the Employee table */
    OracleXMLSave sav = new OracleXMLSave( conn, "Employee" );
    /* setup the XML string */
    String xmlStr = "<?xml version=\"1.0\"?>" +
                    "<ROWSET>" +
                    "  <ROW num=\"1\">" +
                    "    <EMPLOYEE_NO>10</EMPLOYEE_NO>" +
                    "    <FIRST_NAME>Joshua</FIRST_NAME>" +
                    "    <LAST_NAME>Lehman</LAST_NAME>" +
                    "    <DEPT_NO>5</DEPT_NO>" +
                    "  </ROW>" +
                    "</ROWSET>";
    /* insert the row */
    sav.insertXML( xmlStr );
    /* always remember to close the OracleXMLSave object after use */
    sav.close();
  }
}

Like the last example, Listing 4-32 sets up a JDBC Connection to Oracle using the Oracle OCI8 JDBC driver. Doing so creates an instance of the OracleXMLSave class, passing the JDBC Connection object and also the name of the table (in this case Employee). Listing 4-32 then sets up the string used to hold the XML document containing the specification for the row to be inserted into the Employee table.

Listing 4-32 then calls the insertXML( ) method of the OracleXMLSave( ) class to insert the row represented by the XML document specified in xmlStr into the Employee table. Finally Listing 4-32 releases the memory occupied by the instance of OracleXMLSave.

Compiling and running this example has the same requirements as the previous example.

Once it has finished executing you might want to verify for yourself that the row has been inserted into the table. To do this fire up SQL*PLUS and execute the following query:

SQL>SELECT * FROM Employee ORDER BY Employee_ID;

Here is the response you can expect back if Listing 4-32 executed correctly:

EMPLOYEE_NO FIRST_NAME LAST_NAME DEPT_NO
----------- ---------- --------- -------
          1 Sean       Chai            1
          2 Aaron      Con             1
          3 Andrew     Dixon           2
          4 Ted        Bremer          3
          5 Randall    Boseman         4
          6 Jane       Clayton         1
          7 Peter      Connelly        2
          8 Eva        Corets          3
          9 Nate       Sun             5

XMLType

Oracle 9i features a new system-defined object type, XMLType, for storing XML documents. The method, exposed by XMLType, provides mechanisms for creating, extracting, and indexing XML data. XMLType can be used as columns in tables and views. It can also be used in PL/SQL and Java code as parameters, return values, and variables. XMLType has a number of uses, including the four we will discuss in the following sections.

Creating XMLType Columns

As mentioned previously, XMLType can be used as table/view columns. The following example creates a product information table that, among other normal columns, has an XMLType column used to store a product description.

CREATE TABLE Product_Info
(
  Product_Number INTEGER PRIMARY KEY,
  Product_Name VARCHAR2( 20 ) NOT NULL,
  Comments SYS.XMLType NOT NULL
)

Notice that, just like a normal column, you can use the NOT NULL constraint on an XMLType column. However, you cannot currently use default values and check constraints with XMLType columns.

Inserting and Updating XMLType Columns

You can use an INSERT statement to insert an XML document into an XMLType column. If your XML data is stored as a string or CLOB, you first need to convert it to the XMLType before you can insert into an XMLType column. In both cases you use the built-in createXML( ) method that creates and returns an instance of XMLType. You can pass createXML( ) in its argument the XML document as either a string or CLOB argument.

At the time of writing, Oracle only supports insertion of well-formed and complete XML documents. You cannot store fragments or other non-well-formed XML into XMLType columns.

The following example inserts two rows into the Product_Info table using SQL*Plus:

SQL>INSERT INTO Product_Info
      VALUES ( 10, `VideoMan LPV-240', SYS.XMLType.createXML( `<?xml
version="1.0"?><Comments><Comment user=\"tjane\">Nice value but not
enough features</Comment><Comment user=\"rjlehman\">User interface too
complicated</Comment></Comments>' ) );
1 row created.
SQL>INSERT INTO Product_Info
      VALUES ( 12, `Ultra DVD210', SYS.XMLType.createXML( `<?xml
version="1.0"?><Comments><Comment user=\"rhackman\">Positively
Amazing! Best output quality I have seen!</Comment><Comment
user=\"qiwong\">Great video quality. Bad VCD support</Comment>
</Comments>' ) );
1 row created.
SQL>

Here the two XML documents, represented as strings, are first converted to an XMLType by using the createXML( ) method before they are inserted into the Product_Info table. To replace the XML stored in an XMLType column with another, you can simply use the UPDATE statement. As in the case of insertion, unless your new XML is an XMLType, you will have to use the createXML( ) method to convert your XML into an XMLType first.

Here is a simple example of using the UPDATE statement to update one of the rows inserted in the preceding INSERT example:

UPDATE Product_Info
  SET Comments = SYS.XMLType.createXML( `<?xml version="1.0"?>
<Comments><Comment user=\"tjane\">Nice value but not enough features
</Comment><Comment user=\"rjlehman\">User interface too complicated
</Comments><Comment user=\"mgoodman\"> For an off brand this one seems
to be one of the better ones</Comment></Comments>' )
  WHERE Product_No = 10

Using XMLType Columns in SELECT Statements

XMLType can also be used in SELECT statements. In PL/SQL or Java you can use SELECT to transfer XMLType column values to and from XMLType-typed variables. You can also use the built-in getClobValue( ), getStringVal( ), or getNumberVal( ) methods to get the value of an XMLType as a CLOB, varchar, or a number respectively.

For instance, the following SELECT statement displays the XML document stored in the Comments field of Product_Info table:

SQL>SELECT Product_No, Product_Name, Comments.getStringVal() AS Users_Comments
      FROM Product_Info;
PRODUCT_NO PRODUCT_NAME     USERS_COMMENTS
        10 VideoMan LPV-240 <?xml version="1.0"?><Comments><Comment
user=\"tjane\">Nice value but not enough features</Comment><Comment
user=\"rjlehman\">User interface too complicated</Comments><Comment
user=\"mgoodman\"> For an off brand this one seems to be one of
the better ones</Comment></Comments>
        20 Ultra DVD210     <?xml version="1.0"?><Comments><Comment
user=\"rhackman\">Positively Amazing! Best output quality I have
seen!</Comment><Comment user=\"qiwong\">Great video quality. Bad VCD
support</Comment></Comments>
SQL>

Using XPath to Query XML Data

One of the most powerful features of XMLType is the built-in support for advanced XPath XML querying capabilities. XPath support is provided through the ExistsNode( ) and Extract( ) built-in methods. The ExistsNode( ) method is used to check if an XPath expression passed to it as an argument evaluates to at least a single XML element or text node. If the check is true, ExistsNode( ) returns 1. Otherwise, it returns 0. You can use this method anywhere functions can be used.

In the following example we use ExistsNode( ) in the WHERE clause of a SELECT statement to look for rows in the Product_Info table that match a specific XPath expression.

SQL>SELECT Product_No, Product_Name, Comments.getStringVal() AS Users_Comments
      FROM Product_Info
      WHERE Comments.ExistsNode( `/Comments/Comment[@user= mgoodman]' ) = 1
PRODUCT_NO PRODUCT_NAME     USERS_COMMENTS
        10 VideoMan LPV-240 <?xml version="1.0"?><Comments><Comment
user=\"tjane\">Nice value but not enough features</Comment><Comment
user=\"rjlehman\">User interface too complicated</Comments><Comment
user=\"mgoodman\"> For an off brand this one seems to be one of the
better ones</Comment></Comments>
SQL>

The second method supported by XMLType for XPath querying, Extract( ), is used to extract nodes from the XML stored in an XMLType. The result returned from applying an XPath is another XMLType. However, this XMLType does not need to be well-formed; it can consist of a set of nodes or simple scalar types. The following example extends the SELECT statement in the previous example to extract contents from the node that matches the XPath expression.

SQL>SELECT Product_No, Product_Name, Comments.Extract( `/Comments/
Comment[@user=mgoodman]' ).getStringVal() AS USER_COMMENT
      FROM Product_Info
      WHERE Comments.ExistsNode( `/Comments/Comment[@user= mgoodman]' ) = 1
PRODUCT_NO PRODUCT_NAME     USERS_COMMENTS
        10 VideoMan LPV-240 For an off brand this one seems to be one
of the better ones
SQL>
Note: We had to convert the result into a string value by using the
getStringVal() method so that it can be displayed in SQL*Plus.


Conclusion

In this chapter we've seen why corporations all over the globe use XML with databases. We discussed the issues and solutions for exposing data stored in databases as XML documents. We also looked at how XML documents can be persisted as database data. Finally we explored the features provided by some commercial RDBMS products for supporting XML.

In Chapter 5 we will look at another major real-world application of XML: Web development.


Web Development

Though still in its infancy, XML has significantly affected Web page design and deployment. XML brought about a new approach to Web development by encouraging developers to separate content from presentation. This chapter will dive into the new Web paradigm. We will begin with a discussion of the history of Web programming before moving on to discuss how savvy Web developers can use XML to solve practical problems that plague traditional Web page design. Finally we will use the technologies that we learn in this chapter to build a real-world Web site.


The Web Publishing Process

XML makes the Web publishing process much easier than it was before, when Web site programmers relied solely on HTML. To appreciate how XML helps, we're going to take a brief look at the history of Web publishing and see how dismal the situation was before XML appeared. Afterwards we will introduce the example XML document we will use while solving the Web problems of a fictional company.

When Web sites first began popping up, the designers didn't have many options in the form of Web design languages. Designers created Web pages statically using HTML. Though considered old now, HTML does have a number of attractive features. It consists only of rich, plain text, which makes it easy to transfer, store, and view. Armed with a strong knowledge of Cascading Style Sheets (CSS) and table manipulation, a competent Web page designer could create an impressive-looking document.

HTML's significant shortcoming, however, is that it is static. The Web's explosion into commerce heightened the need for dynamic content. Stock quotes, book prices, and inventory information needed to be displayed with immediacy. With these changes came the Common Gateway Interface (CGI), which allowed developers to write programs and scripts, hosted and run by a Web server, to produce dynamic HTML pages. The programs could now process form data, query databases, and build dynamic output.

CGI paved the way for other technologies, such as Microsoft's Active Server Pages (ASP), Allaire's ColdFusion, and many other server-side scripting engines. These technologies offered significant improvements over CGI, such as performance and scalability, and took a giant leap forward through multithreading, a lightweight alternative to creating a new process with each CGI request. Scripts could now be embedded directly into HTML source code, shortening the learning curve for beginning programmers. These tools also made database access for developers easier. Suddenly huge repositories of information could be accessed and updated online. The ubiquitous Web browser made an ideal distributed client as well.

Ironically, the speed of the Web's growth hindered its maturation. Fueled by the promises of riches through e-commerce, companies raced to stake a claim on the Internet. For the developers of sought-after Web products, time to market became more important than sound application design. A paradigm shift began.

Web content underwent a metamorphosis from scholarly publications to corporate billboards to online shopping markets to full-blown enterprise applications. Business logic, database queries, and service communication logic crept into HTML source code. Web pages increased in complexity and began to resemble computer programs instead of publishing documents. A number of the following problems arose with this phenomenon:

  • Scripting languages, while ideal for small amounts of logic, are not robust enough for large-scale applications development. They discourage the use of object-oriented design and do not scale well as an application's complexity grows over time.
  • A tight coupling of presentation and content hinders application extensibility. How many developers have solved browser incompatibilities quickly by copying and pasting chunks of similar code? This solution looks easy until you have several versions of one site to maintain! Similarly, how many hours have Web designers spent changing colors, fonts, and page layouts after a company decided to change its corporate logo or image?
  • A tight coupling of presentation and content leads to a tight coupling of employee skill sets. Many people can design and code a slick Web site or understand complex business rules and database schemas. Only a precious few can do both. How much time is spent fixing bugs in a design specialist's logic or making a code guru's attempt at page layout passable?

Intuitive Web professionals began to see the need to divide the content of a Web page from the rules that govern its display on browsers or small devices. This distinction is what XML seeks to achieve.

An XML Navigation Example

Navigation has always been an important element to any Web site. The developing field of E-commerce placed an even greater emphasis on effective navigation. If potential customers cannot quickly find what they are looking for, they will spend their money elsewhere.

XML is a useful tool for achieving effective Web site navigation. Expandable and collapsible trees are familiar navigation devices but are tricky to implement because they must work with many browsers. Regardless of implementation, all trees share the same conceptual organization. They consist of a number of nodes (or elements) that are assembled in a hierarchical fashion. Such a tree can easily be modeled in an XML document.

Listing 5-1 is an example of such a document. Each <navitem> element contains a description, one or more images, a hyperlink to another document, and an optional list of children.

The data in this example can be modified to represent anything from Web site navigation to a help document's table of contents. In fact, this document is an excerpt from Microsoft's XML 3 Software Development Kit (SDK) documentation. Its generic navigation information can easily be transformed into a browser-specific UI, and Figure 5-1 shows how Microsoft Internet Explorer 5.5 displays this document by using its default transformation.

Figure 5-2 shows the same document transformed into an interface suitable for a user.

Listing 5-1 Navigation.xml: An XML model of a simple navigation tree.

<?xml version="1.0"?>
<navigation type="tree">
  <description>XML (Extensible Markup Language)</description>
  <openImage>/xmlbook/images/general/open.gif</openImage>
  <closedImage>/xmlbook/images/general/closed.gif</closedImage>
  <href target="detail">item2.htm</href>
  <navitem>
    <description>What's New</description>
    <image>/xmlbook/images/general/item.gif</image>
    <href target="detail">item2.htm</href>
  </navitem>
  <navitem>
    <description>Copyright and Legal Information</description>
    <image>/xmlbook/images/general/item.gif</image>
    <href target="detail">item2.htm</href>
  </navitem>
  <navitem>
    <description>XML Developer's Guide</description>
    <openImage>/xmlbook/images/general/open.gif</openImage>
    <closedImage>/xmlbook/images/general/closed.gif</closedImage>
    <href target="detail">item1.html</href>
    <children>
      <navitem>
        <description>Introduction to XML</description>
    <openImage>/xmlbook/images/general/open.gif</openImage>
    <closedImage>/xmlbook/images/general/closed.gif</closedImage>
    <href target="detail">item1.htm</href>
    <children>
      <navitem>
        <description>Advantages of the XML Format</description>
        <image>/xmlbook/images/general/item.gif</image>
        <href target="detail">item1.htm</href>
      </navitem>
      <navitem>
        <description>Creating a Simple XML Document</description>
        <image>/xmlbook/images/general/item.gif</image>
        <href target="detail">item1.htm</href>
      </navitem>
      <navitem>
        <description>Displaying the File in Internet xplorer
</description>
             <image>/xmlbook/images/general/item.gif</image>
             <href target="detail">item1.htm</href>
      </navitem>
    </children>
      </navitem>
    </children>
  </navitem>
  . . . .
</navigation>
Figure 5-1 Internet Explorer displaying the navigation XML document.

Using a New Publishing Technique with XML

Later in this chapter we will explore a real-world example using XML in the Web publishing process. XML will be used to model the different elements of a fictitious company's Web site. The company, Noverant, sells CDs through its online storefront. Noverant wants to convert its site to XML in an effort to lower ongoing maintenance and development costs. The site will support all major browsers (even those without XML support) and will take advantage of some browser-specific features for a richer user experience.

Figure 5-2 A more user-friendly presentation of the navigation XML document.

Although this example is discussed later, it's useful to introduce some of the concepts now so that you can get an early start. The Noverant site will use a design pattern known as Front Controller. This pattern prescribes a centralized access point for all page requests in the site.

All user requests will flow through a single ASP that will perform the following duties:

  1. The controller will check the user's request arguments and will set any relevant security or device-specific flags.
  2. The controller will parse the browser's request string for the requested page.
  3. The controller will call a helper ASP to build an XML model of the requested view.
  4. The controller will choose a user-interface "skin" to govern the transformation of the view XML. The skin will contain the majority of the HTML code that will ultimately be shown in the browser.
  5. The controller will either produce the final HTML document itself or, depending on browser versions and user settings, will return the XML and XSLT code to the client to be processed locally.

By having a single access point act as a proxy to the Web site, it will be much easier to control the flow of information through the site. Global settings can be applied once rather than copied and pasted into every page on the server.


Cascading Style Sheets

CSS is the most familiar method for transforming XML documents for rendering on the Web. CSS styling for XML works much like it does for HTML. Just as traditional CSS applies specialized formatting to generic HTML, it can also apply limited formatting logic to XML so that it can be displayed in a browser. In this section we will go over when to use CSS and browser-compatibility issues.

When to Use CSS to Style XML

CSS cannot significantly alter pieces of an XML document or perform any conditional logic based on the data contained therein. This limitation restricts the use of CSS to transformations where the structure of the resulting HTML looks almost identical to the structure of the original XML document. The way to get around this is to assign formatting properties to each element within the tree. Elements and their children can be hidden or altered in appearance but cannot easily be otherwise rearranged or manipulated. Therefore, you should use CSS when you want to transform an XML document into a document similar in structure to your original. If all you need to do is apply formatting rules to the source XML document, CSS is most likely your tool of choice.

Consider the Listing 5-1 table of contents document shown earlier. Suppose we want to convert that listing into an indented display formatted for the printer. Because the output will be static and the <navitem> elements will be displayed in order, CSS looks like the logical formatting tool. To do this we will start by building a simple style sheet that will create block displays for the elements we want to show.

navigation  { display: block; }
navitem    { display: block; }
description  { display: block; }
href         { display: none; }
openImage      { display: none; }
closedImage  { display: none; }
image    { display: none; }

We can associate this style sheet to our XML document in Figure 5-1 by inserting the line <?xml-stylesheet type="text/css" href="navigation_simple.css"?> just before the <navigation> element. Figure 5-3 shows how this document will look if opened in Internet Explorer 5.5.

Figure 5-3 The same navigation XML source styled by using CSS.

This does not look attractive, but you can see what the CSS processor did to the document. It created block displays for the <navigation>, <navitem>, and <description> elements in place and omitted the <href>, <openImage>, <closedImage>, and <image> elements. Let's jazz it up by altering our style sheet slightly as in Listing 5-2.

This version of the CSS improves the readability of the <navigation> and <navitem> elements by adding font families, colors, sizes, and weights. More importantly, the new stylization rules include <padding> elements. This has the desired effect of incrementally indenting subelements by a fixed amount. The result of this transformation appears in Figure 5-4.

Listing 5-2 A CSS transformation that produces a better-looking result document.

navigation
{
  font-family: verdana;
  color: black;
  font-size: 14;
  font-weight: 800;
  padding: 1em;
  display: block;
}
navitem
{
  font-family: verdana;
  color: black;
  font-size: 11;
  font-weight: 400;
  padding: 1em;
  display: block;
}
description  { display: block; }
href         { display: none; }
openImage      { display: none; }
closedImage  { display: none; }
image    { display: none; }
Figure 5-4 The CSS-styled navigation tree showing indentation of the branches.

How does the CSS processor know to indent items at the same depth in the tree by the same amount? It does so by recursively creating block display areas for each <navitem> element that contains similar block areas for each of that element's children. This process is best illustrated by adding a one-pixel border to the <navigation> and <navitem> elements in the style sheet, as shown in Figure 5-5.

When Not to Use CSS

CSS provides a familiar mechanism for preparing XML documents for rendering on a Web browser. Unfortunately, the capabilities of CSS are limited. Elements can only be transformed in place, meaning that they cannot be reordered. New tags, such as lists and images, cannot be dynamically inserted into the resulting document. The source XML document must be changed to include these tags to accommodate such extensions.

Figure 5-5 The CSS-styled navigation tree with borders to illustrate the padded block elements.

Another drawback to CSS is that it cannot perform logical operations such as "if-then" constructs. Furthermore, CSS is useful only for formatting. It cannot, for example, massage an XML document into another, similar XML document. Web developers must look elsewhere if their transformation requirements transcend simple formatting.

CSS Browser Compliance

One useful feature of CSS is that it is widely supported by popular browsers. Internet Explorer 5.0 and Netscape Navigator 6.0 both support client-side CSS styling of XML documents. Be aware, however, that the major browsers do not currently provide a complete implementation of CSS.


Extensible Stylesheet Language Transformation

The development of CSS leads to the main thrust of this chapter, XSLT, a technology that can perform tighter manipulation of XML than CSS. XSLT is an outgrowth of the XSL developed in 1999 by a W3C committee. The original draft of an XSL specification sought to define how XML documents should be formatted and transformed. This was a large task and the specification attacked each of the requirements separately. In this section we will go over the development of the XSL and XSLT specifications, benefits of their use, and some examples.

XSL Formatting Objects

To accomplish XSL formatting, the XSL specification introduced a new type of XML document that consisted of Formatting Objects. Formatting Objects represent how an XML document should be formatted without regard to typesetting specifics (for example, Microsoft Rich Text Format (RTF), Adobe PDF, and other print languages).

Like CSS and HTML together, an XSL Formatting Object tree describes blocks of content, which might contain text, tables, graphics, and other visual elements. This document provides generic information concerning layout, font characteristics, and color. In order for a Formatting Object document to be converted into a format readable by, say, Microsoft Word, typesetting converters must be used. Typesetting converters take a Formatting Object stream and build documents suitable for a particular renderer, in this case the RTF. In this way Web applications can create a single Formatting Object document that can be converted into many different forms of output. The equations here show how it's done.

Figure 5-6 shows how Formatting Objects and converters can be used to transform a single source XML document into many different types of output.

Figure 5-6 XSL styling with Formatting Objects

This technology is still young, and few Formatting Object converters exist. One exception is the Apache Group's FOP project, which converts Formatting Object documents into Adobe PDF ( http://xml.apache.org ). However, preview releases of XSL formatters that can display XSL-FO natively on the Web are available. Information on the Antenna House XSL Formatter is available at http://www.antennahouse.com/ and the X-Smiles browser is described at http://www.xsmiles.org/.

XSL Transformations

The second part of the XSL specification governing XML transformations matured more quickly than its formatting counterpart. The XSLT language owes much of its rapid adoption to Microsoft, which incorporated an early draft of the specification into its widely distributed Internet Explorer 5.0 browser. Although criticized at the time for releasing software based on an unfinished specification, by placing this powerful technology into the hands of eager programmers, Microsoft exposed a large portion of the developer community to the possibilities XML transformations allow.

Partly because of this sudden, wide distribution of XSL transformation technology, the W3C broke out the transformation component of the XSLT specification and created a new XSLT proposal. Although XSL still contains Formatting Object support, it defers to the XSLT specification for rules governing transformations.

Formally speaking, an XSLT is an XML document that describes how to convert one XML document into another. This sounds trivial, but the applications of the idea are far-reaching. Let's return to the navigation tree we looked at earlier. We can construct an XSLT that converts the XML document into an HTML document for viewing in a Web browser, a Formatting Object XML document for display or conversion into a Word table of contents, or into another XML document all together.

When to Use XSLT

We've seen that CSS can be a useful tool for styling XML when the source document closely resembles the desired display format. Unfortunately, this is rarely the case. The metamorphosis an XML document must often undergo, from generic markup to browser-specific HTML, is significant. Branches must be conditionally selected while HTML elements and script must be inserted into the result document. Sometimes HTML is not even the desired output. This is often the case in e-commerce applications where XML documents must be massaged so they can be exchanged cleanly. In such instances XSLT is the transformation tool of choice.

XSLT Browser Compliance

The downside to XSLT is that it is relatively new and few browsers support it at the time of this writing, which is not surprising when one realizes that not all browsers support CSS 1. So far, Microsoft has pursued the technology aggressively. Version 2.0 of the MSXML parser, which shipped with Internet Explorer 5.0, offered an introduction to XSLT, but was not ready for commercial use. Version 3.0 of the parser offers full support of the XSLT 1.0 specification and includes a multithreaded parser, making it a more viable commercial product. Netscape Navigator does not support XSLT in its version 6.0. Fortunately, developers need not wait for browser support before beginning XML projects. Server-side XSLT transformations provide all of the benefit of XSL processing and none of the worry associated with supporting specific Web browsers.

Using XSL Transformations

Programmers use XSLT to alter XML documents by associating transformation rules with particular nodes in the source tree. Basically, the programmer tells the XSLT engine, "Whenever you encounter this type of node or element, update the result document with this information."

In this example we will create a transformation that converts the tree into an HTML document suitable for rendering on Internet Explorer 5.0 or later. Nodes are selected by using the XPath expression language. XPath is a rich query language designed specifically for filtering the content in an XML document. XPath expressions are always based on a context node and are somewhat similar to directory commands often used in MS-DOS and UNIX shells. Here are some brief examples using the root node of Listing 5-1 as the context node:

  1. navitem Selects all of the navitem nodes directly beneath the root node.
  2. navitem[children] Selects all the navitem nodes that have children elements directly beneath the root node.
  3. //navitem[children] Selects all of the navitem nodes with children elements as children.
  4. navitem[1] Selects the first navitem node.
  5. navitem[last( )] Selects the last navitem node which is a child of the context node.
  6. navitem[last( )]/description Selects the <description> element children of the last navitem node.

Once we've identified the nodes we want to transform, we need to specify what should be done to them. This task is accomplished by using the XSLT <template> element. For example, the following code will replace all occurrences of the <navitem> element with the content of the <description> element before applying further template rules to its children.

<xsl:template match="navitem">
  <xsl:value-of select="description">
  <xsl:apply-templates/>
</xsl:template>

XSL Transformation Examples

Now we will go over an example of an XSLT and embellish that example by adding interactivity. The first line of our example is the style sheet declaration that must appear at the beginning of all XSLT documents. Notice that the XSLT namespace points to http://www.w3.org/1999/XSL/Transform . This address is different from the Internet Explorer 5.0 release of the parser (based on the working draft of the XSL specification) that looked to http://www.w3.org/TR/WD-xsl . The version attribute is mandatory. This line looks like

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

The next line contains the <xsl:output> element that tells the XSLT processor to produce a particular type of document. Possible values for the method attribute are "text", "html", and "xml".

<xsl:output method="html"/>

Now we're in the meat of the transformation. Let's start by selecting the root navigation node of the document and initializing the resulting HTML document.

   <xsl:template match="navigation[@type='tree']">
    <html>
      <head>
        <style>
          a:visited {background-color:white; color:black;
text-decoration: underline}
          a:link {background-color:white; color:black; text-decoration: underline}
          a:active {background-color:activecaption; color:captiontext; text-decoration: underline}
          a:hover {background-color:white; color:black; text-decoration: underline}
          .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;}
          .clsEntryText {padding-top: 2px; padding-left: 20px;
font-family: verdana; color: black; font-size: 11; font-weight: 400; background-color:#FFFFFF;}
          .clsWarningText {font-family: verdana; color: #B80A2D;
font-size: 11; font-weight: 600; width:550;
background-color:#EFE7EA;}
          .clsCopy {font-family: verdana; color: black; font-size: 11; font-weight: 400;  background-color:#FFFFFF;}
        </style>
      </head>

Now we will create a division to hold the contents of the root element in the tree. The division will be useful later when we extend the transformation to support the dynamic expanding and collapsing of tree branches. Also note the {generate-id( )} directive to the XSLT processor. Similar to "<% ... %>" in ASP, braces ("{.}") found within attributes prompt the XSLT processor to evaluate the expression and include the result in the result document. In this case the generate-id( ) function will return a unique alphanumeric id for the current element in the XML source document.

<body>
  <div class="clsHeading" id="open_{generate-id()}">

Next we need to test for the existence of openImage and href elements and display them if they are present.

<xsl:if test="openImage">
  <img src="{openImage}"/>&#160;
</xsl:if>
<xsl:if test="href">
  <a href="{href}">
<xsl:if test="href/@target">
  <xsl:attribute name="target">
    <xsl:value-of select="href/@target"/>
  </xsl:attribute>
</xsl:if>
  <xsl:value-of select="description"/>
  </a>
</xsl:if>
<xsl:if test="not(href)"><xsl:value-of select="description"/></xsl:if>

Finally we need to select all the child <navitem> elements, apply transformation rules to them, and close out the navigation template. The <xsl:apply-templates> element will search for <navitem> elements below the root node, process them, and insert their transformed values into this location in the result tree.

          <xsl:apply-templates select="navitem"/>
        </div>
      </body>
    </html>
  </xsl:template>

That takes care of the root navigation node. Now we need to provide a rule for transforming a generic <navitem> element.

  <xsl:template match="navitem">

The icon we use to display a <navitem> element will depend on whether or not the <navitem> has any children. If the element has children we will display a folder icon.

<xsl:choose>
  <xsl:when test="children">
  <div class="clsEntryText" id="open_{generate-id()}">
      <xsl:if test="openImage">
        <img src="{openImage}"/>&#160;
      </xsl:if>
      <xsl:if test="href">
        <a href="{href}">
      <xsl:if test="href/@target">
        <xsl:attribute name="target"><xsl:value-of select=
"href/@target"/></xsl:attribute>
      </xsl:if>
        <xsl:value-of select="description"/></a>
      </xsl:if>
      <xsl:if test="not(href)">
        <xsl:value-of select="description"/>
      </xsl:if>

Next we need to select all the children <navitem> elements and apply their templates recursively.

<xsl:apply-templates select="children"/>

Finally we handle the case where a <navitem> element has no children.

        </div>
      </xsl:when>
      <xsl:otherwise>
        <div class="clsEntryText">
          <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute></xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>
        </div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Listing 5-3 shows the complete code listing for this transformation.

Listing 5-3 Navigation.xsl: When applied to Navigation.xml, produces a graphical display of the tree.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html"/>
  <xsl:template match="navigation[@type='tree']">
    <html>
      <head>
        <style>
          a:visited {background-color:white; color:black;
text-decoration: underline}
          a:link {background-color:white; color:black;
text-decoration: underline}
          a:active {background-color:activecaption; color:
captiontext; text-decoration: underline}
          a:hover {background-color:white; color:black;
text-decoration: underline}
          .clsHeading {font-family: verdana; color: black;
font-size: 11; font-weight: 800;}
          .clsEntryText {padding-top: 2px; padding-left: 20px;
font-family: verdana; color: black; font-size: 11; font-weight: 400; background-color:#FFFFFF;}
          .clsWarningText {font-family: verdana; color: #B80A2D; font-size: 11; font-weight: 600; width:550;  background-color:#EFE7EA;}
          .clsCopy {font-family: verdana; color: black; font-size: 11; font-weight: 400;  background-color:#FFFFFF;}
        </style>
      </head>
      <body>
        <div class="clsHeading" id="open_{generate-id()}">
          <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>
          <xsl:apply-templates select="navitem"/>
        </div>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="navitem">
    <xsl:choose>
      <xsl:when test="children">
        <div class="clsEntryText" id="open_{generate-id()}">
          <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>
          <xsl:apply-templates select="children"/>
        </div>
      </xsl:when>
      <xsl:otherwise>
        <div class="clsEntryText">
          <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
        </div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

The transformation mentioned in Listing 5-3 will produce a Microsoft Windows Explorer-like tree with all of its branches expanded, which can be seen in Figure 5-7.

Figure 5-7 The navigation tree transformed into a static HTML document by using XSLT.

This example looks much better than the CSS version. We still might want to add a few more features to the transformation, so it might be useful to allow users to expand and collapse branches of the tree dynamically. To do this requires little extra work on the part of the XSLT and makes the tree navigation tool more interactive for the user.

The captiontext color is specific to Internet Explorer and borrows from your Microsoft Windows color settings. Other system colors supported by Internet Explorer include background, scrollbar, window, and appworkspace. See http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/reference/colors/colors.asp for a more complete listing.

An expandable and collapsible tree can be implemented by creating two divisions for each parent node in the tree. One division displays the node collapsed and uses an icon to represent the closed state—a closed book, perhaps. The other division displays the node expanded. Only the expanded division will contain divisions for its children (each of which will also have a collapsed and expanded division).

This transformation will be nearly identical to the static version. The first difference will be to write the JavaScript expand( ) and collapse( ) functions. The script itself is simple. Both functions will expect a division ID to uniquely identify a specific division in the page. We will use the XSL function generate-id( ) to create ID values. The expand( ) and collapse( ) functions will simply toggle the display properties of the open and closed versions of that division.

  <script language="JavaScript">
    function expand(divid)
    {
       eval("closed_"+divid).style.display = 'none';
       eval("open_"+divid).style.display = '';
    }
    function collapse(divid)
    {
       eval("open_"+divid).style.display = 'none';
       eval("closed_"+divid).style.display = '';
    }
  </script>

Next we will create two divisions for each parent node, divid_closed and divid_open. Only one of these divisions will ever be visible at a given time. The first will be the closed division. Notice the onClick( ) event handler attached to the "plus" image. The event handler calls the expand( ) function to toggle the display properties of the open and closed divisions.

<xsl:when test="children">
  <div class="clsEntryText" id="closed_{generate-id()}">
    <xsl:for-each select="ancestor::navitem">
      <xsl:choose>
      <xsl:when test="count(following-sibling::navitem) > 0">
          <img src="/xmlbook/images/line.gif"/>
        </xsl:when>
        <xsl:otherwise>
          <img src="/xmlbook/images/nothing.gif"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
    <img src="/xmlbook/images/folder_plus.gif"
onClick="expand('{generate-id()}')"/>
    <xsl:if test="closedImage">
<img src="{closedImage}"/>&#160;
</xsl:if>
    <xsl:if test="href">
<a href="{href}">
        <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute>
</xsl:if>
        <xsl:value-of select="description"/>
</a>
    </xsl:if>
    <xsl:if test="not(href)">
<xsl:value-of select="description"/>
</xsl:if>
  </div>

The code for the open division is similar except that its display style property is originally set to "none" and it calls the <xsl:apply-templates> XSLT element on its children.

<div class="clsEntryText" id="open_{generate-id()}" style="display:none">
  <xsl:for-each select="ancestor::navitem">
    <xsl:choose>
      <xsl:when test="count(following-sibling::navitem) > 0">
        <img src="/xmlbook/images/line.gif"/>
      </xsl:when>
      <xsl:otherwise>
        <img src="/xmlbook/images/nothing.gif"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>
  <img  src="/xmlbook/images/folder_minus.gif" onClick=
"collapse('{generate-id()}')"/>
  <xsl:if test="openImage"><img src="{openImage}"/>&#160;</xsl:if>
  <xsl:if test="href"><a href="{href}">
    <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute>
</xsl:if>
    <xsl:value-of select="description"/></a>
  </xsl:if>
  <xsl:if test="not(href)">
<xsl:value-of select="description"/>
</xsl:if>
  <xsl:apply-templates select="children"/>
</div>

Listing 5-4 is a complete listing for the dynamic tree transformation. This transformation will produce an interactive HTML representation of the navigation tree with expandable and collapsible branches.

Figure 5-8 shows the results of this transformation on a navigation tree.

Listing 5-4 navigation_dynamic.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
<xsl:output method="html"/>
  <xsl:template match="navigation[@type='tree']">
    <html>
      <head>
        <script language="JavaScript">
          function expand(divid)
          {
             eval("closed_"+divid).style.display = 'none';
             eval("open_"+divid).style.display = '';
          }
          function collapse(divid)
          {
             eval("open_"+divid).style.display = 'none';
             eval("closed_"+divid).style.display = '';
          }
        </script>
        <style>
          a:visited {background-color:white; color:black;
 text-decoration: underline}
          a:link {background-color:white; color:black;
 text-decoration: underline}
          a:active {background-color:activecaption;
color:captiontext; text-decoration: underline}
          a:hover {background-color:white; color:black;
text-decoration: underline}
          .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;}
          .clsEntryText {padding-top: 2px; font-family: verdana; color: black; font-size: 11; font-weight: 400;
background-color:#FFFFFF;}
          .clsWarningText {font-family: verdana; color: #B80A2D; font-size: 11; font-weight: 600; width:550;  background-color:#EFE7EA;}
          .clsCopy {font-family: verdana; color: black; font-size: 11; font-weight: 400;  background-color:#FFFFFF;}
        </style>
      </head>
      <body>
        <div class="clsEntryText" id="open_{generate-id()}">
          <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>
          <xsl:apply-templates select="navitem"/>
        </div>
      </body>
    </html>
  </xsl:template>
<xsl:template match="navitem">
    <xsl:choose>
      <xsl:when test="children">
        <div class="clsEntryText" id="closed_{generate-id()}">
          <xsl:for-each select="ancestor::navitem">
            <xsl:choose>
              <xsl:when test="count(following-sibling::navitem) > 0">
                <img src="/xmlbook/images/line.gif"/>
              </xsl:when>
              <xsl:otherwise>
                <img src="/xmlbook/images/nothing.gif"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
          <img src="/xmlbook/images/folder_plus.gif" onClick="expand('{generate-id()}')"/>
          <xsl:if test="closedImage">
<img src="{closedImage}"/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target">
<xsl:attribute name="target"><xsl:value-of select="href/@target"/>
</xsl:attribute></xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)">
<xsl:value-of select="description"/></xsl:if>
        </div>
        <div class="clsEntryText" id="open_{generate-id()}" style="display:none">
          <xsl:for-each select="ancestor::navitem">
            <xsl:choose>
              <xsl:when test="count(following-sibling::navitem) > 0">
                <img src="/xmlbook/images/line.gif"/>
              </xsl:when>
              <xsl:otherwise>
                <img src="/xmlbook/images/nothing.gif"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
          <img  src="/xmlbook/images/folder_minus.gif"
onClick="collapse('{generate-id()}')"/>
          <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target">
<xsl:attribute name="target">
<xsl:value-of select="href/@target"/>
</xsl:attribute>
</xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
          <xsl:apply-templates select="children"/>
        </div>
      </xsl:when>
      <xsl:otherwise>
        <div class="clsEntryText">
          <xsl:for-each select="ancestor::navitem">
            <xsl:choose>
              <xsl:when test="count(following-sibling::navitem) > 0">
                <img src="/xmlbook/images/line.gif"/>
              </xsl:when>
              <xsl:otherwise>
                <img src="/xmlbook/images/nothing.gif"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
          <img  src="/xmlbook/images/divider.gif" onClick=
"collapse('{generate-id()}')"/>
          <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
        </div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>


An Example Application

Now that we've learned about CSS, XSL, XSLT, and Web publishing in general, let's go through some more difficult examples. In this section we will build a proof-of-concept Web site, based entirely on XML, for a hypothetical company called Noverant.

Figure 5-8 A DHTML version of the navigation tree.

The Requirements

Noverant is an online CD reseller. They want to build a Web site that has the following characteristics.

  1. Navigation is important. They want buyers to be able to quickly focus in on a particular genre or artist. First and foremost, the site needs a tree-based navigator that will allow users to browse the CD catalog without performing multiple keyword searches. The navigator needs to be dynamic becasue the Noverant CD inventory changes hourly.
  2. The site should be easy to maintain. Noverant doesn't have a team of Web developers on staff (that's why they hired you!) that can make complicated changes to HTML. They do, however, have a number of content authors who need to be able to make frequent changes to the listed content listed.
  3. Noverant wants to be able to display live news items related to the music industry. They plan to purchase a subscription to a news feed service where they can obtain articles over the Internet. Noverant wants to embed these articles in the site's content.
  4. The site should accommodate Internet Explorer 4.0 and later versions and Netscape Navigator 4.71 and later versions. This requirement is particularly difficult to achieve in light of Noverant's dynamic navigation requirement.
  5. The site should be extensible. Should Noverant choose to change its corporate image, it wants to be able to adapt its site using minimal effort. New color schemes, layout strategies, browser technologies, and other changes should not require a complete rebuild of the site.
  6. The site should be scaleable. Noverant anticipates a monthly increase of 15 percent in user traffic as they ramp up their marketing efforts over the next six months. The site should be designed to accommodate this increase in load.
  7. The site should be inexpensive. Noverant operates under a tight budget and doesn't have much cash to spend on expensive content servers and third-party software. It wants a Web site built using off-the-shelf components that can get the job done and last until it becomes a publically traded company.

Requirement Analysis

Although Noverant's requirements are aggressive, they are not impossible (especially by using XML). Let's break the requirements down one by one and think about how our knowledge of XML and XSLT might give us a head start.

Navigation

We've already learned how to use XML to build a generic navigation tree that can be transformed into a Windows Explorer-like UI. The interface side of this requirement should be easy; in fact, we already have an XSLT to help render the HTML. The difficulty will reside in accommodating Noverant's ever-changing CD catalog. We'll need to design the XML navigation structure so that it can be built dynamically whenever the page is loaded. One solution is to change the static Listing 5-1 document into ASP.

Maintainability

XML can help us enable Noverant's Web authors to change the content of the site without damaging the presentation logic. We can define a document schema for them whereby they create simple XML content documents that adhere to a specific structure that can be transformed by us into formatted content on the site. This is another example of how separating content from presentation can cleanly divide job roles in a company.

News Feeds

The Newspaper Association of America created an XML schema to define a uniform distribution format for news content. The News Industry Text Format (NITF) specification appeared in 1998 and defines how content providers, such as MSNBC, should distribute their information in XML format. The NITF specification makes it easy for sites such as the Noverant project to have a news feed. By implementing a single XSLT that is compatible with NITF, we can support almost any news feed Noverant is likely to purchase.

Cross-Browser Compatibility

The issue of cross-browser compatibility has plagued Web developers for years. How can you take advantage of special features in Browser A and support Browser B without developing two different Web sites? XSLT, that's how! One approach is to develop a "skins" model for rendering content. Under this model each page request will cause two documents to be generated: 1) a generic XML document containing all of the content that can be displayed, and 2) a browser-specific or user-specific XSLT (the skin), or both, that will handle translating the content into appropriate markup for the display device. Different skins can be programmed to convert the same content into Internet Explorer pages, Navigator pages, or WML pages for hand-held devices. The implication of the skins model, however, is the existence of a controller page that handles locating XML documents and marrying them with the appropriate skin.

Extensibility

The extensibility XML offers, particularly when using a skins-based browsing model, is obvious. Skins can be modified easily to accommodate new color schemes, layout requirements, content schemas, browser advancements, and so forth. You can even design multiple skins with radically different attitudes for each browser. That way a user could change his or her skin and get a whole new Web site.

Scalability

XML opens up a number of different techniques, in addition to traditional hardware clustering approaches, to help with application scalability. The MSXML 3.0 parser includes a free-threaded model to speed concurrent access to the XML processor. The software also improves server-to-server HTTP communication for quicker exchange of XML, SOAP, or both documents. Web developers can also distribute the load of building and transforming documents across different machines. In Noverant's case the logic that constructs the XML navigation document might be off-loaded to another server. Other strategies include caching documents and compiling XSLTs to speed processing time.

Cost

All of the technologies mentioned in this chapter come free with Microsoft Windows NT and Windows 2000 servers or can be obtained without cost from the Microsoft Web site. No additional software components will be necessary.

The Controller

As mentioned previously, we will need to use a page controller design pattern to pull off the XSLT skin model we want to use. The controller will intercept all Web requests and route information according to our architecture. The flow of control will follow these steps:

  1. The client will send an HTTP request to the controller.asp page and will send a URL argument name view (for example, http://www.noverant.com/controller.asp?view=navigation/navigation_frame) in addition to any other arguments the page might require.
  2. The controller will parse the view argument and will call, via HTTP, a page with the name View.asp (for example, http://www.noverant.com/navigation/navigation_frame.asp). The actual location of the ASP page is arbitrary as long as the controller can locate the page based on the view argument alone.
  3. The called ASP, or view helper, will be responsible for constructing a well-formed and valid XML document to be processed by a skin. The document must conform to the Noverant document schema we define. The ASP can use function calls, database requests, HTTP requests, or any other means to populate the XML document.
  4. The controller will use a heuristic to determine an appropriate skin to apply to the XML returned by the view helper. This controller will certainly consider the browser type (Internet Explorer, Navigator, PDA, and so forth), but might also look at user preferences or other settings before deciding on a skin.
  5. Upon choosing a skin, the controller will load the associated XSLT style sheet, process the view XML, and retrieve the transformed document.
  6. The controller will return the result document to the client.

Figure 5-9 shows how the Controller interacts with the page content, the XSLT skin, and the client.

Figure 5-9 A flow-control diagram of the controller architecture.

A simple implementation of the controller is in controller.asp. The first task the controller must perform is to retrieve the view argument from the page request and use this argument to complete the URL to the appropriate helper page. The name of the view helper follows the convention of View.asp.

  // Get the view argument
  view   = Request.QueryString("view")
  base   = Request.ServerVariables("SERVER_NAME")+
Replace(Request.ServerVariables("URL"),"controller.asp","")
  if Request.ServerVariables("HTTPS") = "ON" Then
    base = "https://"+base
  else
    base = "http://"+base
  end if
  xmlurl = base+view+".asp?"

The controller will also need to forward all URL arguments, such as a particular artist, to the helper page. For each item in Request.QueryString, the followig applies:

    if arguments <> "" then arguments = arguments+"&"
    itemvalue = Request.QueryString(item)
    arguments = arguments+item+"="+itemvalue
  Next
  // Append the argument string to the xmlurl
  xmlurl = xmlurl+arguments

Now the controller needs to construct a URL to the XSLT, or skin, responsible for converting the XML data making up the page into the HTML to be returned to the browser. The skin can be chosen based on a variety of factors, including user preference, and browser type. For simplicity's sake the controller will hard-code the skin reference.

  // Contstruct a URL to the appropriate skin
  // This implementation uses only the Internet Explorer 5.0 skin
  // Other skins could be used based on the browser type
  xslurl = base+"xslt/ie5.xsl"

Now that the controller has built URLs to the appropriate helper page and XSLT, it needs to load the source XML and style XSLT into the MSXML2.DOMDocument control. The controller must also check for any parsing errors that might occur along the way.

  // Load the XML
  // A production implementation should use the multi-threaded version
  //of the DomDocument.
  error = false
  Set source = Server.CreateObject("MSXML2.DOMDocument")
  source.async = false
  source.load(xmlurl)
  Set e = source.parseError
  if e.errorCode <> 0 then
    Response.write(e.reason)
    if e.line > 0 Then
      Response.write(e.line)
      Response.write(" ")
      Response.write(e.linepos)
      Response.write(" ")
      Response.write(e.srcText)
    end if
    error = true
  end if
  // Load the XSLT
  Set style = Server.CreateObject("MSXML2.DOMDocument")
  style.async = false
  style.load(xslurl)
  Set e = style.parseError
  Response.write(e.reason)
  if e.errorCode <> 0 Then
    Response.write(e.reason)
    if e.line > 0 Then
      Response.write(e.line)
      Response.write(" ")
      Response.write(e.linepos)
      Response.write(" ")
      Response.write(e.srcText)
    end if
    error = true
  end if

If no errors are found the controller transforms the XML and sends the result document back to the client.

  if error = false Then
    xmlresult = source.transformNode(style)
  end if
  Response.write(xmlresult)
 %>

Building the Pages

Now that we have a working version of the controller, let's begin building the actual view helper pages. The goal of the view helper pages is to delegate the construction of different XML components to smaller, more specialized ASPs. This strategy promotes code reusability and extensibility and makes the site much easier to maintain over the long haul. We will cover how to build different kinds of helper pages that are tuned for specific tasks and how to incorporate these pages into the controller architecture. Finally we will create specific helper pages for displaying the Noverant CD database.

The CD Database

Noverant's CD catalog will be stored in a large XML "database." The CD descriptions are contained in an XML file in the db/ directory. An excerpt from this file follows.

<cds>
<cd>
<id>152</id>
<title>Tallis/Mass for four voices</title>
<artist>Oxford Camerata cond. Jeremy Summerly</artist>
<genre>classical, choral</genre>
</cd>
<cd>
   <id>1</id>
   <title>Great Organ Works/Bach JS</title>
   <artist>Koopman</artist>
   <genre>classical, organ</genre>
</cd>
...
</cds>

All queries against this minidatabase will take the form of XSLT and DOM access. Noverant will likely migrate this information into a more traditional database engine, such as Microsoft's SQL Server, to improve performance as its volume grows. All needs to be changed to accommodate such a transition is an adjustment to the view helper pages to connect to the database, instead of the CD XML document, while constructing the view. Therefore, instead of logic to read and parse the CDs.xml text file, the database-friendly version of the view helper will contain SQL commands to query CD-related tables.

The Noverant Schema

Before we dive into coding the helper pages we will take some time to define the document model that pages in the site will follow by creating an XML schema that describes the site. The significant elements that the site will use are:

  1. <document> The <document> element will form the root node of all content to be shown on the site. You can think of a document node in our document model as serving the same role as the <html> tag in HTML documents.
  2. <navigation> The <navigation> element provides a simple mechanism for defining a hierarchical navigation tree. The XML will contain descriptions, images, hyperlink references, and a list of children elements.
  3. <doclet> A <doclet> will form the primary building block for content on the site. Noverant's content developers will be able to create small <doclets> to define blocks of content to be transformed and incorporated into a page request. A <doclet> will contain a heading, links to one or more images, text, and one or more text excerpts.
  4. <newsitem> A <newsitem> will represent a news story that should appear on the site. The <newsitems> can be authored by Noverant content developers or might be pulled from one or more online news content providers.
  5. <list> The <list> element will represent a list of related items. <list> elements will provide the data model for tables shown in the site. Although the tables will be simple in the first version of the site, the transformation behind them might eventually be extended to allow sorting, grouping, filtering, and other, more advanced reporting features.

    A preliminary version of the schema and a discussion of its elements follows. Note that the schema shown here is an XDR (XML-Data Reduced Language) schema. The MSXML 4.0 parser will support the new XSD (XML Schema Definition) language developed by the W3C. Listing 5-5 shows the schema used for the Noverant Web site.

    Listing 5-5 schema.xml: Describes all the elements and attributes that can be included in a valid Noverant Web page.

    <?xml version="1.0"?>
<Schema name="noverant_dom"
    xmlns="urn:schemas-microsoft-com:xml-data"
    xmlns:dt="urn:schemas-microsoft-com:datatypes">
  <ElementType name="header" content="textOnly" model="closed"/>
  <ElementType name="image" content="textOnly" model="closed"/>
  <ElementType name="p" content="textOnly" model="closed"/>
  <ElementType name="excerpt" content="textOnly" model="closed"/>
  <ElementType name="id" content="textOnly" model="closed"/>
  <ElementType name="text" content="eltOnly" model="closed">
      <element type="p" minOccurs="0" maxOccurs="*"/>
  </ElementType>
  <ElementType name="doclet" order="many" content="eltOnly" model="closed">
      <element type="id" minOccurs="0" maxOccurs="1"/>
      <element type="header" minOccurs="0" maxOccurs="1"/>
      <element type="image" minOccurs="0" maxOccurs="*"/>
      <element type="excerpt" minOccurs="0" maxOccurs="*"/>
      <element type="text" minOccurs="1" maxOccurs="1"/>
  </ElementType>
  <ElementType name="title" content="textOnly" model="closed"/>
  <ElementType name="hl1" content="textOnly" model="closed"/>
  <ElementType name="hl2" content="textOnly" model="closed"/>
  <ElementType name="person" content="textOnly" model="closed"/>
  <ElementType name="bytag" content="textOnly" model="closed"/>
  <ElementType name="location" content="textOnly" model="closed"/>
  <ElementType name="story.date" content="textOnly" model="closed"/>
  <ElementType name="head" order="many" content="mixed" model="open">
      <element type="title" minOccurs="0" maxOccurs="1"/>
  </ElementType>
  <ElementType name="body.end" content="mixed" model="open"/>
  <ElementType name="byline" order="many" content="mixed" model="open">
      <element type="person" minOccurs="0" maxOccurs="*"/>
      <element type="bytag" minOccurs="0" maxOccurs="*"/>
  </ElementType>
  <ElementType name="dateline" order="many" content="mixed" model="open">
      <element type="location" minOccurs="0" maxOccurs="*"/>
      <element type="story.date" minOccurs="0" maxOccurs="*"/>
  </ElementType>
  <ElementType name="headline" order="many" content="eltOnly" model="closed">
      <element type="hl1" minOccurs="1" maxOccurs="1"/>
      <element type="hl2" minOccurs="0" maxOccurs="1"/>
  </ElementType>
  <ElementType name="body.head" order="many" content="eltOnly" model="closed">
      <element type="headline" minOccurs="1" maxOccurs="1"/>
      <element type="byline" minOccurs="0" maxOccurs="*"/>
      <element type="dateline" minOccurs="0" maxOccurs="1"/>
  </ElementType>
  <ElementType name="body.content" order="many" content="mixed" model="open">
      <element type="p" minOccurs="1" maxOccurs="*"/>
  </ElementType>
  <ElementType name="body" order="many" content="eltOnly" model="closed">
      <element type="body.head" minOccurs="1" maxOccurs="1"/>
      <element type="body.content" minOccurs="1" maxOccurs="1"/>
      <element type="body.end" minOccurs="0" maxOccurs="1"/>
  </ElementType>
  <ElementType name="newsitem" order="many" content="eltOnly" model="closed">
      <element type="head" minOccurs="1" maxOccurs="1"/>
      <element type="body" minOccurs="1" maxOccurs="1"/>
  </ElementType>
  <ElementType name="column.name" content="textOnly" model="closed"/>
  <ElementType name="column.description" content="textOnly" model="closed"/>
  <ElementType name="column" order="many" content="eltOnly" model="closed">
      <element type="column.name" minOccurs="1" maxOccurs="1"/>
      <element type="column.description" minOccurs="1" maxOccurs="1"/>
  </ElementType>
  <ElementType name="columns" order="many" content="eltOnly" model="closed">
      <element type="column" minOccurs="1" maxOccurs="*"/>
  </ElementType>
  <ElementType name="property.name" content="textOnly" model="closed"/>
  <ElementType name="property.value" content="textOnly" model="closed"/>
  <ElementType name="property" order="many" content="eltOnly" model="closed">
      <element type="property.name" minOccurs="1" maxOccurs="1"/>
      <element type="property.value" minOccurs="1" maxOccurs="1"/>
  </ElementType>
  <ElementType name="item" order="many" content="eltOnly" model="closed">
      <element type="property" minOccurs="0" maxOccurs="*"/>
  </ElementType>
  <ElementType name="items" order="many" content="eltOnly" model="closed">
      <element type="item" minOccurs="0" maxOccurs="*"/>
  </ElementType>
  <ElementType name="list" order="many" content="eltOnly" model="closed">
      <element type="title" minOccurs="1" maxOccurs="1"/>
      <element type="columns" minOccurs="1" maxOccurs="1"/>
      <element type="items" minOccurs="1" maxOccurs="1"/>
  </ElementType>
  <ElementType name="description" content="textOnly" model="closed"/>
  <ElementType name="openImage" content="textOnly" model="closed"/>
  <ElementType name="closedImage" content="textOnly" model="closed"/>
  <ElementType name="href" order="many" content="textOnly" model="closed">
      <AttributeType name="target" />
  </ElementType>
  <ElementType name="children" order="many" content="eltOnly" model="closed">
      <element type="navitem" minOccurs="0" maxOccurs="*"/>
  </ElementType>
  <ElementType name="navitem" order="many" content="eltOnly" model="closed">
      <element type="description" minOccurs="1" maxOccurs="1"/>
      <element type="image" minOccurs="0" maxOccurs="1"/>
      <element type="openImage" minOccurs="0" maxOccurs="1"/>
      <element type="closedImage" minOccurs="1" maxOccurs="1"/>
      <element type="href" minOccurs="0" maxOccurs="1"/>
      <element type="children" minOccurs="0" maxOccurs="1"/>
  </ElementType>
  <ElementType name="navigation" order="many" content="eltOnly" model="closed">
      <AttributeType name="type" />
      <element type="description" minOccurs="1" maxOccurs="1"/>
      <element type="image" minOccurs="0" maxOccurs="1"/>
      <element type="openImage" minOccurs="0" maxOccurs="1"/>
      <element type="closedImage" minOccurs="1" maxOccurs="1"/>
      <element type="href" minOccurs="0" maxOccurs="1"/>
      <element type="navitem" minOccurs="0" maxOccurs="*"/>
  </ElementType>
    <ElementType name="document" order="many" content="eltOnly" model="closed">
        <element type="navigation" minOccurs="0" maxOccurs="*"/>
        <element type="doclet" minOccurs="0" maxOccurs="*"/>
        <element type="newsitem" minOccurs="0" maxOccurs="*"/>
        <element type="list" minOccurs="0" maxOccurs="*"/>
  </ElementType>
</Schema>

    The first view helper we will write is the navigation view helper. This helper will be responsible for querying the CD database and building a <navigation> tree using ASP script. Noverant wants the <navigation> tree to organize the CD contents by genre and then by artist. When a user clicks on an artist they should see all albums by that artist on the main page.

    We will need to use a combination of XSLT and direct DOM manipulation to construct the tree. XSLT has no easy facility for grouping nodes by a common element, such as genre, and eliminating duplicates. Thus, we will use DOM manipulation to create an unsorted tree of genres and artists and then use XSLT to sort the branches. Listing 5-6 shows the nav_by_genre.asp view helper that is responsible for creating the CD <navigation> tree.

    Listing 5-6 nav_by_genre.asp: ASP page that creates a <navigation> tree out of the genres and artists found in the CD database.

    <%
  xmlurl = Server.MapPath("../db/CDs.xml")
  Set source = Server.CreateObject("MSXML2.DOMDocument")
  source.async = false
  source.load(xmlurl)
  Set e = source.parseError
  if e.errorCode <> 0 then
  Response.write(e.reason)
  if e.line > 0 Then
     Response.write(e.line)
     Response.write(" ")
     Response.write(e.linepos)
     Response.write(" ")
     Response.write(e.srcText)
  end if
  error = true
  end if
  Set genres = Server.CreateObject("MSXML2.DOMDocument")
  Set children = genres.createElement("children")
  genres.appendChild(children)
  Set cds = source.getElementsByTagName("cd")
  for x = 0 To cds.Length - 1
    Set cd = cds.item(x)
    genre = cd.selectSingleNode("genre").text
  if genre <> "" then
    Set existing = genres.selectNodes("children/navitem[description = '"+genre+"']")
    if existing.length = 0 then
      Set navelem  = genres.createElement("navitem")
      Set descelem = genres.createElement("description")
      Set desctext = genres.createTextNode(genre)
      Set imgelem  = genres.createElement("image")
      Set imgtext  = genres.createTextNode("images/item.gif")
      Set oielem  = genres.createElement("openImage")
      Set oitext  = genres.createTextNode("images/open.gif")
      Set cielem  = genres.createElement("closedImage")
      Set citext  = genres.createTextNode("images/closed.gif")
      imgelem.appendChild(imgtext)
      oielem.appendChild(oitext)
      cielem.appendChild(citext)
      descelem.appendChild(desctext)
      navelem.appendChild(imgelem)
      navelem.appendChild(oielem)
      navelem.appendChild(cielem)
      navelem.appendChild(descelem)
      children.appendChild(navelem)
      artist = cd.selectSingleNode("artist").text
      Set artists = genres.createElement("children")
      Set artistnavelem = genres.createElement("navitem")
      Set artistdescelem = genres.createElement("description")
      Set artistdesctext = genres.createTextNode(artist)
      Set artistimgelem = genres.createElement("image")
      Set artistimgtext = genres.createTextNode("images/item.gif")
      artistimgelem.appendChild(artistimgtext)
      artistdescelem.appendChild(artistdesctext)
      artistnavelem.appendChild(artistdescelem)
      artistnavelem.appendChild(artistimgelem)
      artists.appendChild(artistnavelem)
      navelem.appendChild(artists)
    else
      artist = cd.selectSingleNode("artist").text
      Set existingartists = genres.selectNodes("children/
navitem[description = '"+genre+"']/children/navitem[description = '"+artist+"']")
      if existingartists.length = 0 then
        Set artists = existing.item(0).selectSingleNode("children")
        Set artistnavelem = genres.createElement("navitem")
        Set artistdescelem = genres.createElement("description")
        Set artistdesctext = genres.createTextNode(artist)
        Set artistimgelem = genres.createElement("image")
        Set artistimgtext = genres.createTextNode("images/item.gif")
        artistimgelem.appendChild(artistimgtext)
        artistdescelem.appendChild(artistdesctext)
        artistnavelem.appendChild(artistdescelem)
        artistnavelem.appendChild(artistimgelem)
        artists.appendChild(artistnavelem)
      end if
    end if
  end if
  next
  xslurl = Server.MapPath("nav_by_genre.xsl")
  Set style = Server.CreateObject("MSXML2.DOMDocument")
  style.async = false
  style.load(xslurl)
  Set e = style.parseError
  Response.write(e.reason)
  if e.errorCode <> 0 Then
  Response.write(e.reason)
  if e.line > 0 Then
     Response.write(e.line)
     Response.write(" ")
     Response.write(e.linepos)
     Response.write(" ")
     Response.write(e.srcText)
  end if
  end if
  xmldata = genres.transformNode(style)
%>
<navigation type="tree">
  <description>Noverant</description>
  <openImage>images/open.gif</openImage>
    <navitem>
      <description>Welcome to Noverant</description>
      <image>images/item.gif</image>
      <href target="main">controller.asp?view=main</href>
    </navitem>
    <navitem>
      <description>CD's by genre</description>
      <openImage>images/open.gif</openImage>
      <closedImage>images/closed.gif</closedImage>
      <image>images/item.gif</image>
      <children>
<%= xmldata %>
      </children>
    </navitem>
</navigation>

    Listing 5-7 shows the nav_by_genre.xsl transformation. The nav_by_genre.asp script, shown in Listing 5-6, uses this transformation to sort the tree by genre and then by artist.

    Listing 5-7 Nav_by_genre.xsl: Sorts a collection of <navitems> by their description elements.

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html"/>
  <xsl:template match="/children">
    <children>
      <xsl:apply-templates select="navitem">
        <xsl:sort select="description"/>
      </xsl:apply-templates>
    </children>
  </xsl:template>
  <xsl:template match="navitem">
    <navitem>
      <description><xsl:value-of select="description"/></description>
      <openImage><xsl:value-of select="openImage"/></openImage>
      <closedImage><xsl:value-of select="closedImage"/></closedImage>
      <image><xsl:value-of select="image"/></image>
      <xsl:if test="children">
        <children>
          <xsl:apply-templates select="children/navitem">
            <xsl:sort select="description"/>
          </xsl:apply-templates>
        </children>
      </xsl:if>
      <xsl:if test="not(children)">
        <href target="main">controller.asp?view=artist/list&amp;
artist=<xsl:value-of select="description"/></href>
      </xsl:if>
    </navitem>
  </xsl:template>
</xsl:stylesheet>

    Now that we have built a helper to construct a valid <navigation> tree, we're almost finished. Recall that, according to our Noverant schema, a <navigation> element needs a <document> element to contain it before it can be successfully transformed. We will create a front-end helper to wrap the results of the helper we just wrote by using a <document> tag. So why not include the <document> tag in the original helper? Doing so will preclude us from including <navigation> elements in the same document with other elements. The creation of a proxy helper page makes the navigation of the site more extensible. For example, we could later write a "nav_by_artist" helper that is conditionally called by the navigation helper. Listing 5-8 shows the Navigation_frame.asp script, which is responsible for loading the correct navigation scheme.

    Listing 5-8 Navigation_frame.asp: A proxy to call the correct navigation helper.

    <document>
<% Server.execute("nav_by_genre.asp") %>
</document>

    We're done with the navigation helper! Now we need to write the XSLT to render our tree. Our first skin for the site will be designed specifically for the Internet Explorer 5.0 browser. This skin will pull together all the transformation logic for <documents>, <navigationtrees>, <doclets>, <lists>, and <newsitems>.

    The document template will initialize the result document with the proper HTML header, scripts, and style elements. It also creates a container for all of the Noverant document elements by building a table and calling the <xsl:apply-templates> element for all its children. Notice that the document template controls the positioning of the <navigation>, <newsitem>, <doclet>, and <list> child elements.

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
  <xsl:output method="html"/>
  <xsl:template match="document">
    <html>
      <head>
        <script language="JavaScript">
          function expand(divid)
          {
             eval("closed_"+divid).style.display = 'none';
             eval("open_"+divid).style.display = '';
          }
          function collapse(divid)
          {
             eval("open_"+divid).style.display = 'none';
             eval("closed_"+divid).style.display = '';
          }
        </script>
        <style>
          a:visited {background-color:#C7D2DE; color:black;
    text-decoration: underline}
          a:link {background-color:#C7D2DE; color:black;
    text-decoration: underline}
          a:active {background-color:activecaption; color:captiontext; text-decoration: underline}
          a:hover {background-color:#C7D2DE; color:black;
    text-decoration: underline}
          .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;}
          .clsEntryText {padding-top: 2px; font-family: verdana; color: black;
    font-size: 11; font-weight: 400; background-color:#C7D2DE;}
          .clsWarningText {font-family: verdana; white-space:pre; color: #B80A2D;
    font-size: 11; font-weight: 600; width:550;
    background-color:#EFE7EA;}
          .clsCopy {font-family: verdana; color: black; font-size: 11;
    font-weight: 400;  background-color:#FFFFFF;}
          .doclet {margin: 5px; padding:8px; display:block;
    border:solid 1px; padding-top:2px; font-family:verdana; color:black;
    font-size: 13px; font-weight: 400; background-color:white;}
          .docletHeading {padding-top: 2px; font-family: verdana;
    color: #003366; font-size: 20; font-weight: 400;}
          .docletImage {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 11; font-weight: 400;}
          .docletText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 12; font-weight: 400;}
          .docletExcerpt {display:inline; padding-top: 2px;
    font-family: verdana; color: black; font-size: 11; font-weight: 400;}
          .newsItem {margin:5px; vertical-align: top; padding:8px;
    display:inline; border:solid 1px; padding-top:2px; font-family:
    verdana; color:black; font-size: 13px; font-weight: 400;
background-color:white; width:300px}
          .newsHl1 {padding-top: 2px; font-family: verdana; color: #003366;
    font-size: 20; font-weight: 400;}
          .newsHl2 {padding-top: 2px; font-style:italic; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;}
          .newsBlPerson {padding: 2px; font-family: verdana; color: black;
    font-size: 14; text-decoration:underline; font-weight: 400; text-align:right;}
          .newsBlTag {padding: 2px; font-family: verdana; color: black;
    font-size: 11; font-weight: 600; text-align:right;}
          .newsDlLoc {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;}
          .newsDlDate {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;}
          .newsBody {font-family: times; color: black; font-size: 12px;
    font-family:verdana; font-weight: 400; text-align:justify;}
          .list {margin:5px; vertical-align: top; padding:8px;
display:inline; border:solid 1px; padding-top:2px; font-family:
verdana; color:black; font-size: 13px; font-weight: 400;
background-color:white; width:600px}
          .listHeading {padding-top: 2px; font-family: verdana; color: #003366;
    font-size: 20; font-weight: 400;}
          .listText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;}
        </style>
      </head>
      <body style="background-color:#C7D2DE;">
        <table width="100%" cellspacing="5">
          <tr>
            <td nowrap="yes">
              <xsl:apply-templates select="navigation"/>
            </td>
            <td align="left" valign="top">
              <xsl:apply-templates select="doclet"/>
              <xsl:apply-templates select="newsitem"/>
              <xsl:apply-templates select="list"/>
            </td>
            <td>
            </td>
          </tr>
        </table>
      </body>
    </html>
  </xsl:template>

    The next transformation we define is the <list> transformation. The following XSLT will convert a generic list structure into an HTML table.

      <xsl:template match="list">
    <div class="list" id="list_{generate-id()}">
      <table>
        <tr>
          <td colspan="{count(columns/column)}"><font class=
"docletHeading"><xsl:value-of select="title"/></font></td>
        </tr>
        <tr>
          <xsl:for-each select="columns/column">
            <th align="left"><font class="listText"><b><xsl:value-of select="description"/>
    </b></font></th>
          </xsl:for-each>
        </tr>
        <xsl:for-each select="items/item">
          <tr>
            <xsl:for-each select="property">
              <td><font class="listText"><xsl:value-of select=
"property.value"/></font></td>
            </xsl:for-each>
          </tr>
        </xsl:for-each>
      </table>
    </div>
  </xsl:template>

    The <newsitem> transformation will build a news article format similar to those found on professional news sites. Notice that all of the styling is controlled by embedded CSS code.

      <xsl:template match="newsitem">
    <div class="newsItem" id="newsitem_{generate-id()}">
      <div class="newsHl1"><xsl:value-of select="body/body.head/
headline/hl1"/></div>
      <div class="newsHl2"><xsl:value-of select="body/body.head/
headline/hl2"/></div>
      <div class="newsBlPerson"><xsl:value-of select="body/body.head/byline/person"/></div>
      <div class="newsBlTag"><xsl:value-of select="body/body.head/byline/bytag"/></div>
      <div class="newsDlLoc"><xsl:value-of select="body/body.head/dateline/location"/>, </div>
      <div class="newsDlDate"><xsl:value-of select="body/body.head/dateline/story.date"/></div>
      <div class="newsBody">
        <xsl:for-each select="body/body.content/p"><xsl:value-of select="."/><p/></xsl:for-each>
      </div>
    </div>
  </xsl:template>

    The <doclets> will take the form of small, self-contained blocks of content. The transformation for this type of element is relatively straightforward.

      <xsl:template match="doclet">
    <div id="doclet_{generate-id}" class="doclet">
      <div class="docletHeading"><xsl:value-of select="header"/></div>
      <div class="docletText">
        <xsl:if test="image">
          <img height="100" src="images/{image}" align="left"/>
        </xsl:if>
        <xsl:for-each select="text/p"><xsl:value-of select="."/><p/>
    </xsl:for-each>
      </div>
    </div>
  </xsl:template>
</xsl:stylesheet>

    Finally, we need to include the <navigation> transformations into our style sheet. Listing 5-9 shows the IE5 skin in its entirety. The document transformation arranges all the possible components of a Noverant Web page. This transformation is responsible for high-level layout and formatting.

    Listing 5-9 IE5.xsl: First skin for the Internet Explorer 5 browser, covering all of the elements found in the Noverant document schema.

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html"/>
  <xsl:template match="document">
    <html>
      <head>
        <script language="JavaScript">
          function expand(divid)
          {
             eval("closed_"+divid).style.display = 'none';
             eval("open_"+divid).style.display = '';
          }
          function collapse(divid)
          {
             eval("open_"+divid).style.display = 'none';
             eval("closed_"+divid).style.display = '';
          }
        </script>
        <style>
          a:visited {background-color:#C7D2DE; color:black;
    text-decoration: underline}
          a:link {background-color:#C7D2DE; color:black;
    text-decoration: underline}
          a:active {background-color:activecaption; color:
    captiontext; text-decoration: underline}
          a:hover {background-color:#C7D2DE; color:black;
    text-decoration: underline}
          .clsHeading {font-family: verdana; color: black; font-size: 11;
    font-weight: 800;}
          .clsEntryText {padding-top: 2px; font-family: verdana;
    color: black; font-size: 11; font-weight: 400;
background-color:#C7D2DE;}
          .clsWarningText {font-family: verdana; white-space:pre;
    color: #B80A2D; font-size: 11; font-weight: 600; width:550;
background-color:#EFE7EA;}
          .clsCopy {font-family: verdana; color: black; font-size: 11;
    font-weight: 400;  background-color:#FFFFFF;}
          .doclet {margin: 5px; padding:8px; display:block;
    border:solid 1px; padding-top:2px; font-family:verdana; color:black;
    font-size: 13px; font-weight: 400; background-color:white;}
          .docletHeading {padding-top: 2px; font-family: verdana;
    color: #003366; font-size: 20; font-weight: 400;}
          .docletImage {display:inline; padding-top: 2px;
    font-family: verdana; color: black; font-size: 11; font-weight: 400;}
          .docletText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 12; font-weight: 400;}
          .docletExcerpt {display:inline; padding-top: 2px;
    font-family: verdana; color: black; font-size: 11; font-weight: 400;}
          .newsItem {margin:5px; vertical-align: top; padding:8px;
    display:inline; border:solid 1px; padding-top:2px; font-family:
    verdana; color:black; font-size: 13px; font-weight: 400;
background-color:white; width:300px}
          .newsHl1 {padding-top: 2px; font-family: verdana; color: #003366;
    font-size: 20; font-weight: 400;}
          .newsHl2 {padding-top: 2px; font-style:italic; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;}
          .newsBlPerson {padding: 2px; font-family: verdana; color: black;
    font-size: 14; text-decoration:underline; font-weight: 400; text-align:right;}
          .newsBlTag {padding: 2px; font-family: verdana; color: black;
    font-size: 11; font-weight: 600; text-align:right;}
          .newsDlLoc {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;}
          .newsDlDate {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;}
          .newsBody {font-family: times; color: black; font-size: 12px;
    font-family:verdana; font-weight: 400; text-align:justify;}
          .list {margin:5px; vertical-align: top; padding:8px;
    display:inline; border:solid 1px; padding-top:2px; font-family:
    verdana; color:black; font-size: 13px; font-weight: 400;
background-color:white; width:600px}
          .listHeading {padding-top: 2px; font-family: verdana;
    color: #003366; font-size: 20; font-weight: 400;}
          .listText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;}
        </style>
      </head>
      <body style="background-color:#C7D2DE;">
        <table width="100%" cellspacing="5">
          <tr>
            <td nowrap="yes">
              <xsl:apply-templates select="navigation"/>
            </td>
            <td align="left" valign="top">
              <xsl:apply-templates select="doclet"/>
              <xsl:apply-templates select="newsitem"/>
              <xsl:apply-templates select="list"/>
            </td>
            <td>
            </td>
          </tr>
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="list">
    <div class="list" id="list_{generate-id()}">
      <table>
        <tr>
          <td colspan="{count(columns/column)}"><font class=
"docletHeading"><xsl:value-of select="title"/></font></td>
        </tr>
        <tr>
          <xsl:for-each select="columns/column">
            <th align="left"><font class="listText"><b><xsl:value-of
    select="description"/></b></font></th>
          </xsl:for-each>
        </tr>
        <xsl:for-each select="items/item">
          <tr>
            <xsl:for-each select="property">
              <td><font class="listText">
    <xsl:value-of select="property.value"/></font></td>
            </xsl:for-each>
          </tr>
        </xsl:for-each>
      </table>
    </div>
  </xsl:template>
  <xsl:template match="newsitem">
    <div class="newsItem" id="newsitem_{generate-id()}">
      <div class="newsHl1"><xsl:value-of select="body/body.head/
headline/hl1"/></div>
      <div class="newsHl2"><xsl:value-of select="body/body.head/
headline/hl2"/></div>
      <div class="newsBlPerson"><xsl:value-of select="body/body.head/byline/person"/></div>
      <div class="newsBlTag"><xsl:value-of select="body/body.head/byline/bytag"/></div>
      <div class="newsDlLoc"><xsl:value-of select="body/body.head/dateline/location"/>, </div>
      <div class="newsDlDate"><xsl:value-of select="body/body.head/dateline/story.date"/></div>
      <div class="newsBody">
        <xsl:for-each select="body/body.content/p"><xsl:value-of select="."/><p/></xsl:for-each>
      </div>
    </div>
  </xsl:template>
  <xsl:template match="doclet">
    <div id="doclet_{generate-id}" class="doclet">
      <div class="docletHeading"><xsl:value-of select="header"/></div>
      <div class="docletText">
        <xsl:if test="image">
          <img height="100" src="images/{image}" align="left"/>
        </xsl:if>
        <xsl:for-each select="text/p"><xsl:value-of select="."/>
<p/></xsl:for-each>
      </div>
    </div>
  </xsl:template>
  <xsl:template match="navigation[@type='tree']">
    <div class="clsEntryText" style="padding-left:5px" id="open_{generate-id()}">
      <xsl:if test="openImage"><img src="{openImage}"/>&#160;</xsl:if>
      <xsl:if test="href"><a href="{href}">
        <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute> <xsl:if>
        <xsl:value-of select="description"/></a>
      </xsl:if>
      <xsl:if test="not(href)"><xsl:value-of select="description"/>
</xsl:if>
      <xsl:apply-templates select="navitem"/>
    </div>
  </xsl:template>
  <xsl:template match="navitem">
    <xsl:choose>
      <xsl:when test="children">
        <xsl:variable name="childcnt" select="count(.//navitem)"/>
        <div class="clsEntryText" id="closed_{generate-id()}">
          <xsl:for-each select="ancestor::navitem">
            <xsl:choose>
              <xsl:when test="count(following-sibling::navitem) > 0">
                <img src="/xmlbook/images/line.gif"/>
              </xsl:when>
              <xsl:otherwise>
                <img src="/xmlbook/images/nothing.gif"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
          <img src="/xmlbook/images/folder_plus.gif" style=
"cursor:hand" onClick="expand('{generate-id()}')"/>
          <xsl:if test="closedImage"><img src="{closedImage}"
/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
          (<xsl:value-of select="$childcnt"/> artist<xsl:if test="$childcnt != 1">s</xsl:if>)
        </div>
        <div class="clsEntryText" id="open_{generate-id()}" style="display:none">
          <xsl:for-each select="ancestor::navitem">
            <xsl:choose>
              <xsl:when test="count(following-sibling::navitem) > 0">
                <img src="/xmlbook/images/line.gif"/>
              </xsl:when>
              <xsl:otherwise>
                <img src="/xmlbook/images/nothing.gif"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
          <img  src="/xmlbook/images/folder_minus.gif" style=
"cursor:hand" onClick="collapse('{generate-id()}')"/>
          <xsl:if test="openImage"><img src="{openImage}"/>&#160;
</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
           (<xsl:value-of select="$childcnt"/> artist<xsl:if test="$childcnt != 1">s</xsl:if>)
          <xsl:apply-templates select="children"/>
        </div>
      </xsl:when>
      <xsl:otherwise>
        <div class="clsEntryText">
          <xsl:for-each select="ancestor::navitem">
            <xsl:choose>
              <xsl:when test="count(following-sibling::navitem) > 0">
                <img src="/xmlbook/images/line.gif"/>
              </xsl:when>
              <xsl:otherwise>
                <img src="/xmlbook/images/nothing.gif"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
          <img  src="/xmlbook/images/divider.gif" onClick=
"collapse('{generate-id()}')"/>
          <xsl:if test="image"><img src="{image}"/>&#160;</xsl:if>
          <xsl:if test="href"><a href="{href}">
            <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
            <xsl:value-of select="description"/></a>
          </xsl:if>
          <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
        </div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

    Now let's put the code to the test by calling the controller! Figure 5-10 shows the nav_by_genre view helper.

    Figure 5-10 A view of the Noverant navigation tree.

    The Doclet View Helper

    Now we need to write a view helper that will contain the doclet XML. Doclets can be embedded directly in a helper document or can be retrieved dynamically from an external doclet database. For the time being Noverant is willing to write its doclets manually inside a doclet view helper.

    This view helper will be (thankfully!) much easier to write than the navigation helper. Noverant's design decision to write doclets manually means the helper can take the form of the doclet code itself. An example follows:

    <doclet>
  <id>main</id>
  <image>cd.jpg</image>
  <header>Welcome to Noverant</header>
  <excerpt>Do you like tapes or CD's?</excerpt>
  <text>
    <p>
    Headquartered in Raleigh, NC, Noverant.com is revolutionizing the
    way you buy music. We buy directly from the world's leading CD
    publishers to bring the hottest CDs to your door fast!
    </p>
    <p>
    Our award-winning e-commerce site, Noverant.com, leverages the
    power of XML to help you comb through our vast selection of musical
    offerings. Can't find what you're looking for? No sweat! Just drop us a
    line and we'll hunt it down for you.
    </p>
    <p>
    Noverant.com guarantees that you will have your purchase in hand
    within 24 hours of ordering. If you don't we'll send you another one
    for free. Also, make sure to check out our wishlist feature. Don't you
    hate it when you can think of a hundred CDs you want at home, only to
     draw a blank when you walk in the record store? With our wishlist you
    can keep track of the albums you want to buy before you buy them! When
    you're ready to spend some cash you'll know exactly what to get!
    </p>
  </text>
</doclet>

    The News View Helper

    The news view helper will, for now, be as easy to write as the doclet view helper. Valid news items can be written directly into the news view helper. Once Noverant signs their contracts with their news content providers, however, the news view helper will need to be extended to handle remote content. If the content provider has SOAP or FTP services configured to distribute news electronically, the handler can connect to the machine dynamically and download the content. Depending on the format of the vendor's news items, the helper might need to massage the data into Noverant's news format through the use of XSLTs.

    The Artist View Helper

    The artist view helper will be called whenever someone clicks on an artist in the site. The helper can expect to be passed an artist argument by the controller. The helper will query the CD database via an XPath expression to extract all the CDs matching the selected artist. Before we can look up any information about the artist, the CDs.xml database must be loaded. This is accomplished by loading the file into the DOMDocument control.

    <%
  xmlurl = Server.MapPath("../db/CDs.xml")
  Set source = Server.CreateObject("MSXML2.DOMDocument")
  source.async = false
  source.load(xmlurl)
  Set e = source.parseError
  if e.errorCode <> 0 then
  Response.write(e.reason)
  if e.line > 0 Then
     Response.write(e.line)
     Response.write(" ")
     Response.write(e.linepos)
     Response.write(" ")
     Response.write(e.srcText)
  end if
  error = true
  end if

    Extracting all of the CDs for a particular artist is a simple matter for the view helper. The DOMDocument control's selectNodes( ) method allows any XPath expression to be run on the source document.

      artist = Request.QueryString("artist")
  Set cds = source.selectNodes("cds/cd[artist='"+artist+"']")
 %>

    Now that the relevant CDs have been extracted from the database, the view helper must prepare the XML list structure to be returned to the controller. An ASP For-loop will be used to iterate over the collection generated by the XPath expression to create each entry in the list document. Listing 5-10 shows the artist helper page responsible for assembling a Noverant XML document for a specific artist.

    Listing 5-10 List.asp: Retrieving a musician's CDs and constructing an XML result list with the artist view helper page.

    <document>
  <doclet>...</doclet>
  <list>
    <title>
      CD Titles by <%= artist %>
    </title>
    <columns>
      <column>
        <column.name>title</column.name>
        <column.description>CD Title</column.description>
      </column>
    </columns>
    <items>
<%
  For x = 0 to cds.length - 1
    Set cd = cds.item(x)
    title = cd.selectSingleNode("title").text
%>
    <item>
      <property>
        <property.name>title</property.name>
        <property.value>
<%= title %>
        </property.value>
      </property>
    </item>
<% Next %>
    </items>
  </list>
</document>
<%
  xmlurl = Server.MapPath("../db/CDs.xml")
  Set source = Server.CreateObject("MSXML2.DOMDocument")
  source.async = false
  source.load(xmlurl)
  Set e = source.parseError
  if e.errorCode <> 0 then
  Response.write(e.reason)
  if e.line > 0 Then
     Response.write(e.line)
     Response.write(" ")
     Response.write(e.linepos)
     Response.write(" ")
     Response.write(e.srcText)
  end if
  error = true
  end if
  artist = Request.QueryString("artist")
  Set cds = source.selectNodes("cds/cd[artist='"+artist+"']")
 %>
<document>
  <doclet>...</doclet>
  <list>
    <title>
      CD Titles by <%= artist %>
    </title>
    <columns>
      <column>
        <column.name>title</column.name>
        <column.description>CD Title</column.description>
      </column>
    </columns>
    <items>
<%
  For x = 0 to cds.length - 1
    Set cd = cds.item(x)
    title = cd.selectSingleNode("title").text
%>
    <item>
      <property>
        <property.name>title</property.name>
        <property.value>
<%= title %>
        </property.value>
      </property>
    </item>
<% Next %>
    </items>
  </list>
</document>

    Bringing it All Together

    Next we will build a page to combine our doclets and news items together into a single document to be displayed on the site. This page will be easy to author; all it needs to do is rely on the view helpers that we've already written.

    <document>
  <% Server.execute("doclet/doclet.asp") %>
  <% Server.execute("news/news.asp") %>
</document>

    The main page can be tested by issuing an HTTP request directly to the controller and referencing the view in this URL string:

    http://localhost/xmlbook/chapter 8/noverant/controller.asp?view=main

    Figure 5-11 is the result.

    Figure 5-11 The front page for the site, including a <doclet> and a <newsitem> transformation.

    This looks great! All that's left for the front page is to create a logo and put together a frame set, as shown in Listing 5-11 through Listing 5-13.

    Listing 5-11 index.htm: This file contains the frame set that organizes the layout of the site.

    <html>
<title>Noverant Inc</title>
<frameset rows="55,*" frameborder="no" framespacing="0">
  <frame name="masthead" src="masthead.htm" marginwidth="0"
marginheight="0" scrolling="no" frameborder="no" framespacing="0">
  <frame name="body" src="main.htm" marginwidth="0" marginheight="0"
    scrolling="no" frameborder="no">
</frameset>
</html>

    Listing 5-12 masthead.htm: Contains an image to be displayed at the top of all pages in the site.

    <html>
  <body>
    <img src="images/logo.gif">
  </body>
</html>

    Listing 5-13 main.htm: Divides frame into two vertical sections, one for global navigation and the other for site content.

    <html>
<title>Noverant Inc</title>
<frameset cols="20%,*" frameborder="yes" framespacing="0">
  <frame name="nav" src="controller.asp?view=navigation/navigation_frame"
    marginwidth="5" marginheight="0" scrolling="auto" frameborder="no" framespacing="0">
  <frame name="main" src="controller.asp?view=main" marginwidth="0"
    marginheight="0" scrolling="auto" frameborder="no">
</frameset>
</html>
    Figure 5-12 The full front page, with separate frames for the masthead, navigation tree, and main body.

    Figure 5-12 shows how it looks when we load Listing 5-11 in Internet Explorer. Figure 5-13 shows the artist view helper in action.

    Figure 5-13 A list of CD titles, displayed by clicking on the name by the selected musician.


Final Thoughts

Building Noverant's Web site was a useful project in beginning to explore the capabilities of XML, XSLT, and the XML DOM. As good as this site looks for a proof-of-concept piece, it needs more work before it will be ready for production use.

The document model is somewhat simplistic. The <doclet> element in particular needs to be expanded to accommodate different media types, relative priority, and advanced layout requirements. The query page can be enhanced to provide a more flexible query mechanism for a user. More skins need to be written to handle different browsers. The XSLT style sheet(s) should be able to handle more intelligent positioning than is currently possible. The doclet helper should allow for better management of content in the site. Finally the controller should be changed to take advantage of the multithreaded XML parser.

In this chapter we introduced some new XML technologies, such as XSLT, and have seen how they can impact traditional Web application design. The Noverant Web storefront served as a useful example of a Web site designed around XML. Hopefully you have picked up a few ideas that will be of use to you in your Web applications.

By separating presentation from content, XML can help to solve many of the maintenance problems that have plagued traditional Web applications. I recommend that you begin thinking about ways in which you can incorporate XML into new Web projects. In Chapter 6 we will see how XML can be used to develop application user interfaces.


Building User Interfaces

Software developers have struggled with creating User Interfaces (UIs) since the birth of the computer, and these interfaces have constantly evolved, even when the tasks they perform have remained the same. A noticeable example can be seen in the progression of Microsoft's operating systems. From MS-DOS to Windows 3 to Windows XP, Microsoft has improved the usability of its most current operating system, so the task of installing, managing, and launching applications are easier than ever.

Similarly, other UIs for desktop and enterprise applications have changed over time. Advances in computing such as faster computers and graphics hardware now enable developers to build graphic-rich applications. The Microsoft Foundation Classes (MFC) armed application developers with a suite of standard, yet powerful UI components. By using these tools, developers can easily include such features as drag-and-drop, cut-and-paste, multimedia, and event-driven logic.

A current interface topic concerns Web browsers. The popularity of the Internet and the Web have made the Web browser ubiquitous. First-generation Web browsers were meant for viewing content, not building application UIs. The browser, however, was forced to adapt as the Internet grew into a forum for thousands of distributed applications. Developers struggled to create browser-based UIs that were functional at best. Today developers can build browser-based, thin-client applications that are nearly as rich as their thicker cousins.

This chapter will discuss how developers can leverage XML to help them with UI layout, navigation, and input controls. We will explore ways to use XML to create a layer of abstraction on top of traditional UI elements, making them more dynamic and extensible. Finally we will put these ideas to us, in actual code samples.


Modeling a User Interface in XML

The most important trait of an effective UI is consistency because an application with a consistent approach to human interaction is much easier to learn and use. Consistent UIs can also, with practice, be used intuitively. Intuitive use means efficient use; efficient users mean happy users; and happy users mean well-paid developers.

After consistency, the look and feel of an application's UI can be its strongest asset or its worst detractor. Naturally, users see an application's outward appearance first, and from that they draw first impressions. It behooves the software developer to design an interface that is sharp-looking and consistent in its visual presentation. Now you have a new tool for making sure the presentation says what you want it to say.

XML can help enforce visual consistency across the application by wresting the power of making UI design decisions from the hands of content developers and returning it to its original owner, the visual designer. XML can also help incrementally augment a UI's palette of components as new tool sets become available. Thus, the implementation or behavior of a UI component can be modified without altering the underlying model. In addition, separating presentation from content allows visual designers and content developers to work in concert, shortening the project lifecycle.

No matter the nature of the application, all UIs share a number of features, such as layout and navigation. In this section we will identify and describe several of these UI features.

Layout

The layout of an application's UI governs the positioning of its content. Information should be presented so that it is easy to find, manipulate, and analyze. Data should be presented in a clean, uncluttered manner. Closely related information should be tightly grouped and separated from less relevant information. The positioning of material need not be flashy. Simplicity and efficiency are what we want.

Navigation

An intuitive navigation scheme is critical to an application's reception. Users should be able to find the function or item they want quickly and intuitively. Similar functions should be grouped together for easy access. Navigation elements should not relocate themselves. Consistent behavior on the part of the user should be encouraged by the application.

Input Controls and Widgets

Input widgets, or components, are the atomic building blocks out of which all UIs are created. Examples include text fields, text boxes, check boxes, and any other controls that solicit input from the user. Some widgets are interchangeable. For example, combo boxes and radio buttons can often be used interchangeably to ask the user to "choose one of the following options." An application should not alternate between these controls arbitrarily.

User Actions

Actions are used to represent the various ways a user can manipulate data on the screen. Actions can take the form of buttons, pull-down menus, right-click menus, and others. Some actions are global, such as "undo" actions. Other actions are context-specific.

Security

Security in an enterprise application should be flexible enough to accommodate various dynamic roles that accurately reflect the job responsibilities of many different classes of users. An application's security should control the navigation, actions, and data exposed to the user. It should not be intrusive; a restricted user should feel that the application is limited to what they can see.

Extensibility

A well-designed Web UI will take into account changes or variations in browser capabilities. The UI should be able to accommodate more sophisticated browser controls with a minimum of tweaking. Web browsers are dynamic software packages. New features and bug-fixes are constantly being added. Therefore, you should design a UI that is well-suited to change.

Customizability

Flexible UIs should allow some degree of user customization, but interfaces that are too highly customizable create needless complexity. An application should not go overboard by offering the ability to change every label, font, or screen. Ideally, allowing some customization to views, reports, color schemes, and the overall look and feel will make your application more user-friendly.

Color Scheme

Color can be used to improve an application's usability and aesthetic appeal and can have a utilitarian purpose when applied well. For example, colors can be used to separate or associate different content blocks within the UI. All graphical programs within the Windows operating system inherit the environment's color scheme. Additionally, the active window usually has a uniquely colored caption for identification purposes.


XML Benefits

XML can be used in all facets of an application's UI to enforce consistency and offer improved extensibility. By separating content from presentation, XML can help maintain a natural division between the roles of content development and aesthetic design. In this section we will look at various ways in which XML can be incorporated into UI design.

The Controller Pattern Revisited

In Chapter 5 we saw how XML and XSLT can streamline the design, development, and ongoing maintenance of a corporate Web site. At the core of the site was an implementation of the controller design pattern. The idea behind this pattern was to route all requests to the underlying system through a single ASP. This page acted as a proxy for the user, stepping in to render the page before it was returned to the browser.

By architecting a Web application in this way, designers can leverage XML and XSLT to split the process of page construction and presentation into separate tasks. The following sequence describes the chain of events that is initiated whenever a user requests a new page inside the Web application.

  1. The user issues a request to the controller, sending in the name of the page they want to see as well as any contextual information (for example, record IDs, user IDs, etc) that page might need to do its job (for example, see http://www.noverant.com/controller.asp?view=cddetail&cdid=10013).
  2. The controller redirects the request to a page handler that assembles the view the user requested (for example, cddetail1.asp). Like any ASP, the handler might query a database, read a file from the Web server's file system, or even call another URL to gather the data to be shown on the page. Unlike normal ASP pages, however, the handler does not build an HTML document. Instead, it creates an XML document whose structure is specific to the application and was dictated by the application's UI architect.
  3. The page handler returns the page's XML content back to the controller. Now that the controller has an abstract XML version of the page, it must transform the XML version into a form suitable for viewing on the user's browser (for example, HTML). The controller delegates the transformation to an XSLT page built specifically for the job.
  4. The XSLT adheres to the same XML document structure that the page handler did when it built the document. The transformation extracts the generic data from the XML document, massages it, applies layout and formatting rules, inserts any necessary script, and delivers the finished product to the controller.
  5. The controller delivers the final document to the client.

Listing 6-1 shows an implementation of the controller design pattern using ASPs.

Listing 6-1 controller.asp: a simple implementation of the controller ASP page.

<%
    // Get the view argument
    view   = Request.QueryString("view")
    base = Request.ServerVariables("SERVER_NAME")+
Replace(Request.ServerVariables("URL"),"controller.asp","")
    if Request.ServerVariables("HTTPS") = "ON" then
        base = "https://"+base
    else
        base = "http://"+base
    end if
    xmlurl = base+view+".asp?"
    arguments = ""
    // Parse additional arguments
    For Each item in Request.QueryString
        if arguments <> "" then arguments = arguments+"&"
        itemvalue = Request.QueryString(item)
        arguments = arguments+item+"="+itemvalue
    Next
    // Append the argument string to the xmlurl
    xmlurl = xmlurl+arguments
    // Contstruct a URL to the appropriate skin
    // This implementation uses only the Internet Explorer 5.0 skin
    // Other skins could be used based on the browser type
    if Request.QueryString("skin") <> "" then
        skin = Request.QueryString("skin")
    else
        skin = "ie5"
    end if
    xslurl = base+"xslt/"+skin+".xsl"
    // Load the XML
    // A production implementation should use the multi-threaded
version of the DomDocument.
    error = false
    Set source = Server.CreateObject("MSXML2.DOMDocument")
    source.async = false
    tmp = source.setProperty("ServerHTTPRequest", true)
    source.load(xmlurl)
    Set e = source.parseError
    if e.errorCode <> 0 then
        Response.write(e.reason)
        if e.line > 0 Then
            Response.write(e.line)
            Response.write(" ")
            Response.write(e.linepos)
            Response.write(" ")
            Response.write(e.srcText)
        end if
        error = true
    end if
    // Load the XSLT
    Set style = Server.CreateObject("MSXML2.DOMDocument")
    style.async = false
    tmp = style.setProperty("ServerHTTPRequest", true)
    style.load(xslurl)
    Set e = style.parseError
    if e.errorCode <> 0 Then
        Response.write(e.reason)
        if e.line > 0 Then
            Response.write(e.line)
            Response.write(" ")
            Response.write(e.linepos)
            Response.write(" ")
            Response.write(e.srcText)
        end if
        error = true
    end if
    if error = false Then
        xmlresult = source.transformNode(style)
    end if
    Response.write(xmlresult)
 %>

This approach to Web site design can also be applied to UI design for Web applications. It offers a number of benefits. Splitting page rendering into isolated construction and formatting tasks enables parallel development activity and shorter development timelines. Furthermore, these distinct activities can be assigned to specialists. Content authors have greater knowledge of the intricacies of the application's business logic and the various ways used to pull information from the back-end. Layout specialists can focus on the aesthetics of the UI, worrying less about where the information comes from and more about how it should be presented.

This approach is also extensible. The controller can be augmented to decorate or filter XML content based on a user's browser or role within the application. New presentation skins, or transformations, can be plugged into the environment without rewriting every page. This approach also discourages monolithic page design. By promoting the segregation of content from presentation, the controller pattern makes an application easier to maintain over the long run.

The User Interface Schema

Team members need to work together to make a project succeed. In this case content developers and layout designers can communicate in a common, well-defined language by using XML and XSLT to implement a controller-based UI. This language, or schema, defines the documents that flow through the application. The schema acts as a blueprint for content developers when they begin building pages as well as a specification the presentation group uses when constructing their XSLT. The schema is used to validate the documents in the application, and it should address common UI aspects such as layout, navigation, input controls, user actions, and security. Once the schema is finalized, the two groups can begin working independently, so get this out of the way first.

Defining your UI this way forces your team to think abstractly about what they're trying to show in the application. This process yields immediate returns. Presentation specialists will see ways to interpret the UI XML documents in more interesting ways. System architects will uncover newer uses for XML within the program. And once you've defined your project's UI schema, content developers can begin building conformant documents before the presentation rules are even implemented!

Two Approaches to User Interface Schemas

The goal of your UI schema is to define an XML document model that can be easily transformed into UI elements within your application. The nature of this schema depends on many factors. How broad is your application? How many different business entities are involved? What types of clients will be using the system? Will your application need to support a wide range of browsers with different capabilities (such as an Internet application) or just a single browser (an intranet application)?

Generally speaking, your UI schema can follow two broad categories. The schema can be presentation based, in which the elements are associated with UI functions instead of the type of data shown. The XSLTs for this type of schema translate the generic UI controls into browser-specific documents. Conversely, the schema can be business object-based. XML documents under this paradigm model business entities and contexts instead of generic forms. More XSLT work is required in this scenario, as it is responsible for creating the entire UI.

Presentation-Based User Interfaces

Presentation-based UI documents are easiest for traditional HTML developers to understand. Presentation-oriented schemas define generic document structures that all pages in the system must follow. Note that the HTML specification given here is only one example of a presentation-based schema.

Advantages to using a presentation-based UI schema include

  • A shorter learning curve. The approach is similar to HTML and is easy for HTML developers to understand.
  • A small number of required transformations. This schema requires a transformation template for each piece of the generic content model. New transformations are unnecessary even if the number of pages in the system grows.
  • More consistent-looking pages. Associating the schema and transformations with document structure will enforce consistency among the pages in your application.

Disadvantages to this approach include:

  • Lost meta-information about the underlying data in the page. The schema does not care what kind of data is being displayed as long as it has a valid document structure.
  • Difficulty in defining highly dynamic pages. Some pages in your application will require a lot of client-side scripting to handle user interaction. This can be difficult to achieve without hard coding the scripts directly in the pages.

Therefore, use a presentation-based UI schema if the number of different business entities or the number of pages in your application is large. A presentation-based schema is durable, scaleable, and evenly divides UI responsibilities between content developers and layout designers.

To show you how this type of schema can be useful, Listing 6-2 illustrates what the XML for a specific person might look like under a presentation-based schema.

Listing 6-2 An example of a presentation-based XML document.

<document>
  <header>
    <title>Person Detail</title>
    <icon>person.gif</icon>
  <section>
    <header>
      <title>William Jones</title>
    </header>
    <view>
      <properties>
        <property description="First Name">William</property>
           <property description="Last Name">Jones</property>
             <property description="Address">200 Irving Street</property>
           <property description="City">Cambridge</property>
           <property description="State">MA</property>
           <property description="Zip Code">02138</property>
        </properties>
      </view>
    </section>
  </document>

This document looks similar to an HTML document, although XML has no hard-coded presentation rules. Instead the layout designer can use a number of clues when rendering this page. Still, not much meta-information describes the nature of the data that the document contains.

Business Object-Based User Interfaces

The second category of UIs is a business object-centric approach to UI XML documents. In this case the underlying XML documents represent business entities instead of document structures. Here XML documents describe the type of data being shown (for example, CD, employee, or automobile) on the page and the context of the page (for example, detail, edit, or creation). The XSLT developer must create transformations that build unique documents for every type and context combination allowed by the system.

Advantages to using an object-based UI schema include

  • More specialized pages. If certain object contexts in your application call for highly dynamic and custom pages, an object-based approach might be the way to go.
  • Preserved metadata. XML documents that abide by a business object-oriented schema provide information about what the data is instead of how to display it. Metadata can be useful if your application makes heavy use of it on the client-side.

Some of the disadvantages to an object-based approach are:

  • It does not scale well as the number of business entities or their contexts grow. Multiple transformation templates covering a variety of contexts must be implemented whenever a new business entity is introduced.
  • Consistency in look and feel is more difficult to achieve. Highly-specialized pages might thwart valiant efforts to install a global look and feel.
  • Global changes are more difficult. Presentation rules might be scattered across a number of different object transformation templates. These should be localized if possible.
  • This approach places a heavy burden on the XSLT author.

Accounting for the advantages and disadvantages, use an object-based UI schema when your application has few business entities with highly specialized UI requirements. This approach will help you isolate dynamic content into browser-specific XSLTs. Client-side scripts are generated by the XSLTs instead of by the content developers.

The following code details what a business object-based XML document might look like.

<person context="detail">
  <firstName>William</firstName>
  <lastName>Jones</lastName>
  <address>200 Irving Street</address>
  <city>Cambridge</city>
  <state>MA</state>
  <zip>02138</zip>
</person>

This XML document is noticeably different in structure from the presentation-based document shown previously. The XML elements in this file only relay information about the type of data viewed and the context where it is utilized. The XSLT author is responsible for packaging this information into UI elements. The XSLT might also need to request additional XML data (such as the person's company information) via a separate HTTP request.

The User Interface Schema in This Chapter

At this point we will adopt a presentation-oriented XML schema for later examples. The examples are not too complex and the time-saving benefits that a presentation-based schema provides outweigh any concerns we might have about dynamic content. For your application, however, you should pick the UI schema approach that works best in your environment. You might even decide to implement a combination of the two approaches, in which specific object-based XML documents are transformed into generic presentation-based XML documents.


Layout Management

One of XML's best features is that it enforces global consistency. This is easily implemented through the use of the controller design pattern. In this section we will use XML to define a unique layout model for designing a simple UI. If you recall the Noverant Web site in Chapter 5, the controller pattern prescribes a single front-end to all page requests in the application. The controller has the following two main jobs:

  1. Gather the XML that makes up the content of the requested page. Often, this task is not handled directly by the controller. Doing so would make the controller too complex. Delegating this responsibility to view helpers that are specifically designed for each view is easier.
  2. Apply presentation rules to the content by transforming the XML content into a final document to be delivered to the client. The presentation engine is free to produce any type of document suited to a particular context. It might create an HTML document intended for a Web browser, a Wireless Markup Language (WML) document for a wireless device, a PDF or Microsoft Excel document for reporting purposes, an XML document to be processed further by another application, or just plain text.

A Simple Detail View

To begin our examination of the controller, let's look at a simple XML document that will serve as a foundation for our document model. Listing 6-3 is an excerpt from the Noverant back-office application in which a user has requested detailed information about a particular CD.

Listing 6-3 CDDetail1.xml: a generic document describing how a CD should be displayed.

<document>
  <header>
    <title>This is our first XML application page</title>
    <icon>document.gif</icon>
  </header>
  <section>
    <header>
      <title>Hello World</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property description="Property 1">Hello World</property>
      </properties>
    </view>
  </section>
</document>

The <document> element indicates the beginning and end of the content in this document. Like the <html> element in HTML, the <document> element is a container for all the information underneath it. The <document> element might contain a <header> element with a title and an icon.

The <section> element represents an area of content on the screen. Zero, one, or more <section> elements might appear inside a <document> element. The <section> element might also have a <header> element and one or more <view> elements beneath it. A <view> element can house many forms of content. The content in a <view> might be generic (for example, XML documents that describe property/value pair listings or generic input forms) or it might be a specific entity (for example, an XML document that describes a specific business object).

In this example our document contains a single <section> and <view> and a small property sheet. Suppose we transform this document into an HTML page suitable for rendering in our application. We will create an XSLT style sheet to perform this transformation. The first lines of the style sheet are used for initialization. The <xsl:stylesheet> element signifies the beginning of a transformation, and the <xsl:output> element indicates that the result of this transformation will be an HTML document.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/
Transform" version="1.0">
<xsl:output method="html" />

First we will match the root <document> node and initialize our HTML document. After inserting the <html> element the transformation creates a <head> element and provides a link to the CSS page that will be used to govern fine-grained formatting in the application. The guts of the transformation affect the body of the document. Inside the <body> element, the transformation creates a <div> element to house the rest of the information in the page. Notice that this master <div> inherits style attributes from the document class that appears in our CSS. The final step is to call <xsl:apply-templates/> to delegate further transformations to more precise rules.

<xsl:template match="document">
  <html>
    <head>
      <link rel="stylesheet" type="text/css" href="css/style.css"/>
    </head>
    <body>
      <div class="document">
        <xsl:apply-templates/>
      </div>
    </body>
  </html>
</xsl:template>

The document's header contains information about the page as a whole. The header might contain a title and a link to an icon. This information will be displayed at the top of the screen. The document header template matches on the "document/header" XPath expression to distinguish a <document> element's header from a <section> element's header.

The first task of this transformation template is to create a division for the header. The division links to the documentHeader class in our style sheet. Next the template checks for the existence of an <icon> element, transforming it into an <img> element if it exists. Notice that the transformation inserts an absolute path to the image directory before inserting the value of the icon. Next the template gets the value of the <title> element and inserts it into the result document.

<xsl:template match="document/header">
  <div class="documentHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>

The section template is similar to the document template. It creates a wrapper division with padding to set it apart from other sections that might appear in the page. It then creates a new division, referring to the section CSS class and makes a recursive call to <xsl:apply-templates>.

<xsl:template match="section">
  <div style="padding:20px;" align="center">
    <div class="section">
      <xsl:apply-templates/>
    </div>
  </div>
</xsl:template>

The section header template is, again, similar to the document header template. It creates a new division and inserts the icon and title found in the XML document.

<xsl:template match="section/header">
  <div class="sectionHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>

In this example the view template does not need to do anything other than look for further templates to apply. This will be extended in later examples.

<xsl:template match="view">
  <xsl:apply-templates/>
</xsl:template>

We will transform the document's <properties> and <property> elements into a simple table containing name/value pairs. The properties template sets up the beginning and ending <table> elements and defers to the property template to create the individual rows. Properties might also include hyperlinks to other documents. The XSLT tests for the existence of an href attribute and creates a hyperlink if one exists.

<xsl:template match="properties">
  <table cellpadding="5">
    <xsl:apply-templates/>
  </table>
</xsl:template>
<xsl:template match="property">
  <tr>
    <td bgcolor="a1a1a1">
      <font class="text"><b><xsl:value-of select="@description"/></b></font>
    </td>
    <td><font class="text">
      <xsl:choose>
        <xsl:when test="@href"><a href="{@href}"><xsl:value-of select="."/></a></xsl:when>
        <xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
      </xsl:choose>
    </font></td>
  </tr>
</xsl:template>
</xsl:stylesheet>
Try to use <xsl:apply-templates/> instead of <xsl:for-each/> wherever possible. This syntax will allow you to mix and match components more easily.

The following CSS example, Listing 6-4, contains all the CSS rules for this document.

Listing 6-4 Style.css: a CSS for providing fine-grained element formatting.

body {
  background-color: background;
}
.text {
  font-family: Verdana;
  font-size:9pt;
  color: black;
}
.document {
  background-color: background;
  border:0px;
  width: 100%;
}
.documentHeader {
  border:1px solid;
  width: 100%;
  background-color:778899;
  border-bottom:1px solid;
  font-weight: bold;
  color:white;
  font-family: Verdana;
  font-size:9pt;
}
.section {
  border:0px;
  width: 99%;
  text-align: left;
  background-color:white;
  border: 3px;
  border-style: outset;
  border-color: e0e0e0;
  color:white;
  font-family: Verdana;
  font-size:9pt;
}
.sectionHeader {
  background-color: activeCaption;
  color: captionText;
  align: left;
  border:0px;
  width: 100%;
  border-bottom:1px solid;
  font-family: Verdana;
  font-size:9pt;
}
Although XSLT is the language of choice for most XML transformations, CSS should still be used to isolate formatting rules for your HTML documents.

What does the XML document look like after the XSL and CSS styling rules are applied? Figure 6-1 shows the results of our transformation.

Figure 6-1 Microsoft Internet Explorer displaying a simple application page transformation.

What have we accomplished here? For starters, we have created a 30-line HTML document out of an 18-line XML document. Although the addition of 12 lines does not sound like much, it amounts to a 60 percent inflation ratio. Therefore, we see that XML can do much of the "dirty work" of page creation for you. A layout specialist needs only to program the XSLT once and the XML engine will create and recreate thousands of lines of fine-tuned HTML code. You don't have to worry about un-closed tags (XSLT requires that all style sheets are well formed, and this feature catches a lot of HTML errors) or plenty of copied-and-pasted formatting logic running rampant in your application. Furthermore, global style changes need to be applied only a single time to a single document to affect every page.

Another detail to notice in this example is that the source XML document is simple. It boils everything down to the essential content to be displayed on the page. This becomes more evident when we look at a more intricate example.

A More Detailed View

Consider the Noverant CD On-Line storefront from the previous example. Presumably, Noverant uses a back-office application to maintain its database of albums and artists. This application allows Noverant employees to browse the inventory, update the database, and easily link to more detailed information. Listing 6-5 is an example of an XML document that contains information about one of the popular albums in the collection.

Listing 6-5 CDDetail2.asp: XML document containing a second section element to show the CD's track listing.

<document>
  <header>
    <title>The Beatles - Revolver</title>
    <icon>document.gif</icon>
  </header>
  <section>
    <header>
      <title>General Information</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property description="Artist">The Beatles</property>
        <property description="Album">Revolver</property>
        <property description="Label">EMI Records Ltd</property>
        <property description="Year Released">1966</property>
        <property description="Price">$13.99</property>
      </properties>
    </view>
  </section>
  <section>
    <header>
      <title>Track Listing</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property description="Track 1">Taxman [2:39] (Harrison)</property>
        <property description="Track 2">Eleanor Rigby [2:07] (Lennon/McCartney)</property>
        <property description="Track 3">I'm Only Sleeping [3:01] (Lennon/McCartney)</property>
        <property description="Track 4">Love You To [3:01] (Harrison)</property>
        <property description="Track 5">Here, There, and Everywhere [2:25] (Lennon/McCartney)</property>
        <property description="Track 6">Yellow Submarine [2:40] (Lennon/McCartney)</property>
        <property description="Track 7">She Said She Said [2:37] (Lennon/McCartney)</property>
        <property description="Track 8">Good Day Sunshine [2:09] (Lennon/McCartney)</property>
        <property description="Track 9">And Your Bird Can Sing [2:01] (Lennon/McCartney)</property>
        <property description="Track 10">For No One [2:01] (Lennon/McCartney)</property>
        <property description="Track 11">Doctor Robert [2:15] (Lennon/McCartney)</property>
        <property description="Track 12">I Want to Tell You [2:29] (Harrison)</property>
        <property description="Track 13">Got to Get You Into My Life [2:30] (Lennon/McCartney)</property>
        <property description="Track 14">Tomorrow Never Knows [2:57] (Lennon/McCartney)</property>
      </properties>
    </view>
  </section>
</document>

This XML document, when transformed with the XSLT listed earlier, produces an HTML page with two content areas. This page is shown in Figure 6-2. Notice that the transformation took care of positioning and styling the information in the result document.

Figure 6-2 A more interesting example of an XSLT-generated application page

These rules can be modified easily by tweaking the XSLT. Suppose that Noverant would like to preserve real estate on its Web pages by adopting a "tab-folder" approach to content layout (instead of stacking content sections on top of one another). This can be achieved by modifying the section template of our XSLT.

Layout specialists can begin experimenting with different rendering techniques once your XML schema has been defined. Little cost is associated with making global styling changes, as these rules are localized in your XSLT document.

A code listing of the new version of the transformation follows. JavaScript was added to the top of the page to handle tab switching. The section template has been augmented to hide all sections except the first one and to display a row of buttons that activate other content sections. Two new classes, tabButton and activeTabButton, were added to the CSS to control the formatting of the tab buttons.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="document">
  <html>
    <head>
      <link rel="stylesheet" type="text/css" href="css/style.css"/>
      <xsl:if test="header/title"><title><xsl:value-of select="header/title"/></title></xsl:if>

The following script implements the "tabbing" action in the screen. The function accepts the ID of a tab, which is brought to the foreground by setting its display property. The function then iterates over the other tabs, making them invisible by setting their display style properties to none.

      <script language="javaScript">
        <![CDATA[
          function showTab(section, scnt) {
            for (i=0; i < scnt; i++) {
              t = document.getElementById("s_"+i);
              if (t) {
                if (i == section) {
                  t.style.display = '';
                } else {
                  t.style.display = 'none';
                }
              }
            }
          }
        ]]>
      </script>
    </head>
    <body>
      <div class="document">
        <xsl:apply-templates/>
      </div>
    </body>
  </html>
</xsl:template>
<xsl:template match="document/header">
  <div class="documentHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>

The section transformation template contains the bulk of the changes that are necessary to implement tab folders. The template must now keep track of sibling <section> elements to display their tabs at the top. The logic in this template creates darkened tabs for all of the sibling sections and a highlighted tab for the context section. Each of the siblings tabs, or buttons, contains an onClick( ) event handler that calls the showTab( ) JavaScript function to bring that section to the foreground.

<xsl:template match="section">
  <xsl:variable name="numsections" select="count(../section)"/>
  <xsl:variable name="precedingsibs"
select="count(preceding-sibling::section)"/>
  <xsl:variable name="sectionid" select="concat('s_',$precedingsibs)"/>
  <div align="center">
    <xsl:if test="$precedingsibs = 0">
      <xsl:attribute name="style">padding-top:20px</xsl:attribute>
    </xsl:if>
    <div id="{$sectionid}" align="center" class="section">
      <xsl:if test="$precedingsibs > 0">
        <xsl:attribute name="style">display:none</xsl:attribute>
      </xsl:if>
      <xsl:apply-templates select="header"/>
      <xsl:if test="$numsections > 1">
        <div align="left">

Here the XSLT creates tab buttons for all the previous sections in the document.

          <xsl:for-each select="preceding-sibling::section">
            <xsl:choose>
              <xsl:when test="header/shortTitle">
                <button id="button{position}" class="tabButton" onClick="showTab({position()-1},
{$numsections})"><xsl:value-of select="header/shortTitle"/></button>
              </xsl:when>
              <xsl:otherwise>
                <button id="button{position}" class="tabButton"
onClick="showTab({position()-1},{$numsections})"><xsl:value-of
select="header/title"/></button>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>

The XSLT now creates a tab for the current section node. Notice that the CSS class attached to the button has changed to activeTabButton to indicate a selected tab.

          <xsl:choose>
            <xsl:when test="header/shortTitle">
              <button id="button{position}" class="activeTabButton"
onClick="showTab({$precedingsibs},{$numsections})">
<xsl:value-of select="header/shortTitle"/></button>
            </xsl:when>
            <xsl:otherwise>
              <button id="button{position}" class="activeTabButton"
onClick="showTab({$precedingsibs},{$numsections})">
<xsl:value-of select="header/title"/></button>
            </xsl:otherwise>
          </xsl:choose>

Here the XSLT template iterates over all of the following <section> elements. These tabs are also marked as inactive.

          <xsl:for-each select="following-sibling::section">
            <xsl:choose>
              <xsl:when test="header/shortTitle">
                <button id="button{position}" class="tabButton"
onClick="showTab({$precedingsibs+position()},{$numsections})">
<xsl:value-of select="header/shortTitle"/></button>
              </xsl:when>
              <xsl:otherwise>
                <button id="button{position}" class="tabButton"
onClick="showTab({$precedingsibs+position()},{$numsections})">
<xsl:value-of select="header/title"/></button>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
        </div>
      </xsl:if>
      <xsl:apply-templates select="view"/>
    </div>
  </div>
</xsl:template>

The rest of the template remains unchanged.

<xsl:template match="section/header">
  <div class="sectionHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>
<xsl:template match="view">
  <xsl:apply-templates/>
</xsl:template>
<xsl:template match="properties">
  <table cellpadding="5">
    <xsl:apply-templates/>
  </table>
</xsl:template>
<xsl:template match="property">
  <tr>
    <td bgcolor="a1a1a1">
      <font class="text"><b><xsl:value-of select="@description"/>
</b></font>
    </td>
    <td><font class="text">
      <xsl:choose>
        <xsl:when test="@href"><a href="{@href}">
<xsl:apply-templates /></a></xsl:when>
        <xsl:otherwise><xsl:apply-templates /></xsl:otherwise>
      </xsl:choose>
    </font></td>
  </tr>
</xsl:template>
</xsl:stylesheet>

The effects of applying the new transformation to the same source XML document can be seen in Figure 6-3.


List Views

At this point we've seen how you can use XML and XSLT to create generic content blocks of property listings. This is a familiar approach to displaying related information to a user. Sometimes, however, other layout constructs are more appropriate. For example, the CD Track Listing section might be better shown in a list where each row represents a track with separate columns for the track number, title, length, and author. To accomplish this we need to extend our XML schema to support a simple list construct. We will begin by looking at the portion of our XML schema that defines a valid list.

Figure 6-3 A new transformation applied to the Listing 6-5 document to allow tab-based navigation of content sections.

The top level of a list XML document will contain a description and a reference to a default icon that will be displayed on each row. This icon can be overridden by a reference to a row-specific icon.

<ElementType name="icon" content="textOnly" model="closed"/>
<ElementType name="list.description" content="textOnly" model="closed"/>

The list will also need a collection of columns that define the different types of fields that each row will contain. All columns will have a name, description, and data type. A sorted column will indicate whether or not the data is sorted in ascending or descending order.

<ElementType name="list.column.sortdirection" content="textOnly" model="closed"/>
<ElementType name="list.column.name" content="textOnly" model="closed"/>
<ElementType name="list.column.description" content="textOnly" model="closed"/>
<ElementType name="list.column.datatype" content="textOnly" model="closed"/>
<ElementType name="list.column" order="many" content="eltOnly" model="closed">
    <element type="list.column.sortdirection" minOccurs="0"
maxOccurs="1"/>
    <element type="list.column.name" minOccurs="1" maxOccurs="1"/>
    <element type="list.column.description" minOccurs="1"
maxOccurs="1"/>
    <element type="list.column.datatype" minOccurs="1" maxOccurs="1"/>
</ElementType>

A list's collection of column elements will roll up into a parent columns element.

<ElementType name="list.columns" order="many" content="eltOnly" model="closed">
    <element type="list.column" minOccurs="0" maxOccurs="*"/>
</ElementType>

All rows in the list will have a collection of properties representing the data to be shown in the row. The list.property element contains a list.property.name element that matches the value of one of the list.column.name elements as well as a corresponding value element and an optional hyperlink element.

<ElementType name="list.property.name" content="textOnly" model="closed"/>
<ElementType name="list.property.value" content="textOnly" model="closed"/>
<ElementType name="list.property.href" content="textOnly" model="closed"/>
<ElementType name="list.property" order="many" content="eltOnly" model="closed">
    <element type="list.property.name" minOccurs="1" maxOccurs="1"/>
    <element type="list.property.value" minOccurs="1" maxOccurs="1"/>
    <element type="list.property.href" minOccurs="0" maxOccurs="1"/>
</ElementType>

No list would be complete without support for grouping rows by a common column value. Summarizing information this way can be an effective means of organizing data. This will be accomplished in our schema by inserting list.group elements that indicate the column being grouped, the common value for items in the same group, and a list of children that share that value.

<ElementType name="list.group.name" content="textOnly" model="closed"/>
<ElementType name="list.group.description" content="textOnly" model="closed"/>
<ElementType name="list.group.value" content="textOnly" model="closed"/>
<ElementType name="list.children" order="many" content="eltOnly" model="closed">
  <element type="list.item" minOccurs="0" maxOccurs="*"/>
  <element type="list.group" minOccurs="0" maxOccurs="*"/>
</ElementType>
<ElementType name="list.item" order="many" content="eltOnly" model="closed">
  <element type="list.property" minOccurs="0" maxOccurs="*"/>
<element type="icon" minOccurs="0" maxOccurs="1"/>
</ElementType>
<ElementType name="list.group" order="many" content="eltOnly" model="closed">
  <element type="list.group.name" minOccurs="1" maxOccurs="1"/>
  <element type="list.group.description" minOccurs="1" maxOccurs="1"/>
  <element type="list.group.value" minOccurs="1" maxOccurs="1"/>
  <element type="list.children" minOccurs="1" maxOccurs="1"/>
</ElementType>

Finally the list element type rolls up all its components into a single node.

<ElementType name="list" order="many" content="eltOnly" model="closed">
  <element type="icon" minOccurs="1" maxOccurs="1"/>
  <element type="list.description" minOccurs="1" maxOccurs="1"/>
  <element type="list.columns" minOccurs="1" maxOccurs="1"/>
  <element type="list.children" minOccurs="0" maxOccurs="1"/>
</ElementType>
Remember to avoid characters such as <, >, and & in your XML data. These characters can creep into your XML pages, particularly when your content is retrieved dynamically from a database, and cause run-time XSLT errors.

Now we replace the bland property-based track listing with one that uses our new list schema. This XML document is much larger than the simpler version we saw before. Generating the code behind such listings can be tedious and error-prone when done by hand. It would be wise to invest time in creating server-side scripts to help automate the process.

<document>
  <header>
    <title>The Beatles - Revolver</title>
    <icon>CD.gif</icon>
  </header>

The first section of this document stays the same as before.

  <section>
    <header>
      <title>General Information</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property href="controller.asp?view=artistdetail&amp;id=10001"
description="Artist">The Beatles</property>
        <property description="Album">Revolver</property>
        <property href="controller.asp?view=labeldetail&amp;id=10001"
description="Label">EMI Records Ltd</property>
        <property description="Year Released">1966</property>
        <property description="Price">$13.99</property>
      </properties>
    </view>
  </section>

The second section is replaced by an XML list construct. The list view begins with information describing the columns that will appear in the table.

  <section>
    <header>
      <title>Track Listing</title>
      <icon>document.gif</icon>
    </header>
    <view>
      <list>
        <icon>CD.gif</icon>
        <list.description>The Beatles / Revolver: Track Listing
</list.description>
        <list.columns>
          <list.column>
            <list.column.sortdirection>asc</list.column.sortdirection>
            <list.column.name>TRACK</list.column.name>
            <list.column.description>Track</list.column.description>
            <list.column.datatype>numeric</list.column.datatype>
          </list.column>
          <list.column>
            <list.column.name>TITLE</list.column.name>
            <list.column.description>Song Title</list.column.description>
            <list.column.datatype>text</list.column.datatype>
          </list.column>
          <list.column>
            <list.column.name>LENGTH</list.column.name>
            <list.column.description>Length</list.column.description>
            <list.column.datatype>text</list.column.datatype>
          </list.column>
          <list.column>
            <list.column.name>WRITER</list.column.name>
            <list.column.description>Song Writer</list.column.description>
            <list.column.datatype>text</list.column.datatype>
          </list.column>
        </list.columns>

We create <listitem> elements for each song on the album. Each listitem, or row in the table, contains properties for each column defined in the table. The ordering of the properties within the <listitem> does not matter; the columns collection at the root of the document control the column order.

        <list.children>
          <list.item>
            <list.property>
              <list.property.name>TRACK</list.property.name>
              <list.property.value>1</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>TITLE</list.property.name>
              <list.property.value>Taxman</list.property.value>
              <list.property.href>controller.asp?view=
trackdetail&amp;id=10001</list.property.href>
            </list.property>
            <list.property>
              <list.property.name>LENGTH</list.property.name>
              <list.property.value>3:01</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>WRITER</list.property.name>
              <list.property.value>Harrison</list.property.value>
            </list.property>
          </list.item>
          <list.item>
            <list.property>
              <list.property.name>TRACK</list.property.name>
              <list.property.value>2</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>TITLE</list.property.name>
              <list.property.value>Eleanor Rigby</list.property.value>
              <list.property.href>controller.asp?view=
trackdetail&amp;id=10002</list.property.href>
            </list.property>
            <list.property>
              <list.property.name>LENGTH</list.property.name>
              <list.property.value>2:07</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>WRITER</list.property.name>
              <list.property.value>Lennon/McCartney
</list.property.value>
            </list.property>
          </list.item>
          <list.item>
            <list.property>
              <list.property.name>TRACK</list.property.name>
              <list.property.value>3</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>TITLE</list.property.name>
              <list.property.value>I'm Only Sleeping
</list.property.value>
              <list.property.href>controller.asp?view=
trackdetail&amp;id=10003</list.property.href>
            </list.property>
            <list.property>
              <list.property.name>LENGTH</list.property.name>
              <list.property.value>3:01</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>WRITER</list.property.name>
              <list.property.value>Lennon/McCartney
</list.property.value>
            </list.property>
          </list.item>
          <list.item>
            <list.property>
              <list.property.name>TRACK</list.property.name>
              <list.property.value>4</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>TITLE</list.property.name>
              <list.property.value>Love You To</list.property.value>
              <list.property.href>controller.asp?view=
trackdetail&amp;id=10004</list.property.href>
            </list.property>
            <list.property>
              <list.property.name>LENGTH</list.property.name>
              <list.property.value>3:01</list.property.value>
            </list.property>
            <list.property>
              <list.property.name>WRITER</list.property.name>
              <list.property.value>Harrison</list.property.value>
            </list.property>
          </list.item>
        </list.children>
      </list>
    </view>
  </section>
</document>

Finally we need to extend our XSLT so that it understands what it should do with the elements defined in our list schema. The first template will match the list element. Its purpose will be to initialize an HTML table that will contain the list.

<xsl:template match="list">
  <table class="list" cellspacing="0" cellpadding="0">
    <tr>
      <td valign="top" nowrap="yes">
        <table width="100%" cellpadding="0" cellspacing="1">

After inserting the table in the result document, the transformation creates table headers for each of the <list.column> elements in the XML document.

        <tr bgcolor="a0a0a0" style="padding:2px;spacing-top:0px">
          <td width="1%" class="text" align="center">
            <img src="images/envelope.gif" width="16"/>
          </td>
          <xsl:for-each select="list.columns/list.column">
            <td class="text" align="center" style="color:white;
font-weight:bold">
              <xsl:value-of select="list.column.description"/>
              <xsl:variable name="sort-dir" select=
"list.column.sortdirection"/>
              <xsl:if test="($sort-dir = 'asc')">
                <img src="images/up.gif" hspace="4"/>
              </xsl:if>
              <xsl:if test="($sort-dir = 'desc')">
                <img src="images/down.gif" hspace="4"/>
              </xsl:if>
            </td>
          </xsl:for-each>
        </tr>

The transformation then applies further templates to the list.children collection to generate the table body. Finally the list template appends a footer to the table to provide a summary of the number of lines listed.

        <xsl:apply-templates select="list.children"/>
        </table>
      </td>
    </tr>
    <tr bgcolor="e0e0e0">
      <td class="text">
        <xsl:variable name="childrencount" select="count
(.//list.item)"/>
        <xsl:if test="$childrencount=1">
          <xsl:value-of select="$childrencount"/> item
        </xsl:if>
        <xsl:if test="$childrencount!=1">
          <xsl:value-of select="$childrencount"/> items
        </xsl:if>
      </td>
    </tr>
  </table>
</xsl:template>

The job of the list.item template is to build a table row for each <list.item> element in the document. The template loops over the list.columns collection, locates a list.property with a matching name, gets its value from the <list.property.value> element, and fills in a table cell with that value. The template also includes rules to handle embedded icons and hyperlinks.

<xsl:template match="list.item">
  <xsl:variable name="current-item" select="."/>
  <xsl:variable name="current-row" select="position()"/>
  <xsl:variable name="id" select="concat('v',generate-id())"/>
  <tr>
    <xsl:choose>
      <xsl:when test="position() mod 2">
        <xsl:attribute name="style">
          background-color:ffffff
          <xsl:if test="count(ancestor::list.group) > 0">
            ;display:none
          </xsl:if>
        </xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
        <xsl:attribute name="style">
          background-color:f1f1f1
          <xsl:if test="count(ancestor::list.group) > 0">
            ;display:none
          </xsl:if>
        </xsl:attribute>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:attribute name="id"><xsl:value-of select="$id"/>
</xsl:attribute>
    <td class="text" align="center" valign="top">
      <xsl:variable name="iconurl" select="ancestor::list/icon"/>
      <xsl:choose>
        <xsl:when test="icon and icon != ''">
          <img width="16" height="18"><xsl:attribute name="src">
images/<xsl:value-of select="icon"/></xsl:attribute></img>
        </xsl:when>
        <xsl:otherwise>
          <img width="16" height="18"><xsl:attribute name="src">
images/<xsl:value-of select="$iconurl"/></xsl:attribute></img>
        </xsl:otherwise>
      </xsl:choose>
    </td>
    <xsl:for-each select="ancestor::list/list.columns/list.column">
      <xsl:variable name="current-property" select="list.column.name"/>
      <xsl:variable name="current-datatype" select=
"list.column.datatype"/>
      <xsl:variable name="current-position" select="position()"/>
      <xsl:variable name="last-position" select="last()"/>
      <xsl:variable name="current-description" select=
"list.column.description"/>
      <xsl:for-each select="$current-item/list.property
[list.property.name=$current-property]">
        <td class="text" valign="top">
          <xsl:attribute name="title"><xsl:value-of select=
"$current-description"/>: <xsl:value-of select="list.item.value"/>
</xsl:attribute>
          <xsl:choose>
            <xsl:when test="($current-datatype = 'numeric' or
$current-datatype='monetary' or $current-datatype='date' or
$current-datatype='percentage')">
              <xsl:attribute name="align">right</xsl:attribute>
            </xsl:when>
            <xsl:otherwise>
              <xsl:attribute name="align">left</xsl:attribute>
            </xsl:otherwise>
          </xsl:choose>
          <xsl:if test="list.property.value">
            <xsl:choose>
              <xsl:when test="list.property.href != ''">
                <xsl:element name="a">
                  <xsl:attribute name="href"><xsl:value-of
select="list.property.href"/></xsl:attribute>
                  <xsl:value-of select="list.property.value"
disable-output-escaping="yes"/>&#160;
                </xsl:element>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="list.property.value"
disable-output-escaping="yes"/>&#160;
              </xsl:otherwise>
            </xsl:choose>
          </xsl:if>
        </td>
      </xsl:for-each>
    </xsl:for-each>
  </tr>
</xsl:template>

The list.group template is written to handle n-levels of groupings within a list. The template assumes that all items in the list have been grouped prior to the transformation. Therefore, a different program must be used to associate the similar line items to be grouped.

<xsl:template match="list.group">

The following line determines how much space you should use when indenting the group summary lines. The desired stairstep effect can be achieved by multiplying a constant by the number of list.group ancestors the node has.

  <xsl:variable name="padding" select="20 * count(ancestor::list.group)"/>
  <xsl:variable name="id" select="concat('v',generate-id())"/>

The template next creates a row to display the group summary information. The row's display style property is set to none if it's a child of another list.group. A plus sign is inserted into the row that, when clicked, will expand the group and display all its children. (The JavaScript code to do this will be listed following this transformation.)

  <tr>
    <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute>
    <xsl:if test="ancestor::list.group">
      <xsl:attribute name="style">display:none</xsl:attribute>
    </xsl:if>
    <td>
      <xsl:attribute name="id"><xsl:value-of select="$id"/>_plus
</xsl:attribute>
      <xsl:attribute name="colspan"><xsl:value-of select="1 +
count(ancestor::list/list.columns/list.column)"/></xsl:attribute>
      <table width="100%" cellpadding="0" cellspacing="0" border="0" class="listgroup">
      <xsl:attribute name="id"><xsl:value-of select="$id"/>_table
</xsl:attribute>
      <tr>
        <xsl:if test="$padding != 0">
          <td>
            <xsl:attribute name="width">
<xsl:value-of select="round($padding)"/></xsl:attribute>
            <xsl:attribute name="style">background-color:727272
</xsl:attribute>
            <img src="images/spacer.gif" height="1">
              <xsl:attribute name="width">
<xsl:value-of select="round($padding)"/></xsl:attribute>
            </img>
          </td>
        </xsl:if>
        <td width="5" style="cursor:hand">
          <img src="images/plus.gif" hspace="4" style="border:1px outset">
            <xsl:attribute name="onClick">ExpandCollapse('<xsl:for-each
select="list.children/*[name() = 'list.group' or name() =  'list.item']">
<xsl:value-of select="concat('v',generate-id())"
/><xsl:if test="last() > position()">,</xsl:if></xsl:for-each>','<xsl:value-of
select="$id"/><xsl:for-each select=".//*[name() = 'list.group' or name() =  'list.item']">,
<xsl:value-of select=
"concat('v',generate-id())"/></xsl:for-each>')</xsl:attribute>
            <xsl:attribute name="id"><xsl:value-of select="$id"
/>_plus_image</xsl:attribute>
          </img>
        </td>

The summary information, including property description, value, and item count, is inserted into the result document. Finally the template makes a call to <xsl:apply-templates> to transform child list.item and list.group nodes.

        <td align="left" class="text">
          <xsl:value-of select="list.group.description"
/>&#160;:&#160;<xsl:value-of select="list.group.value"/>
          <xsl:variable name="childrencount" select="count
(.//list.item)"/>
          <xsl:if test="$childrencount=1">
            (<xsl:value-of select="$childrencount"/> item)
          </xsl:if>
          <xsl:if test="$childrencount>1">
            (<xsl:value-of select="$childrencount"/> items)
          </xsl:if>
        </td>
      </tr>
      </table>
    </td>
  </tr>
  <xsl:apply-templates select="list.children/*"/>
</xsl:template>
The table-based implementation of the function shown here is much more complicated than its <div> element-based cousin. Because <div> elements are block displays, their display properties are inherited by all their descendants.

As promised, here is the expandCollapse( ) JavaScript function that opens and closes table groupings. This function is inserted into the document by the XSLT shown previously.

function ExpandCollapse(c1,ca) {
  var ca_a    = ca.split(',');
  var c1_a   = c1.split(',');
  c1 = document.getElementById(ca_a[1]);
  if (c1.style.display == 'none')
    expand = true;
  else
    expand = false;
  thePlus = ca_a[0]+"_plus_image";
  plus_div = document.getElementById(thePlus);
  if (expand)
  {
    if (plus_div)
      plus_div.src = "images/minus.gif";
    for (i=0; i < c1_a.length; i++)
    {
      theRow = document.getElementById(c1_a[i]);
      theRow.style.display = '';
      plus_div = document.getElementById(c1_a[i]+"_plus_image");
      if (plus_div)
        plus_div.src = "images/plus.gif";
    }
  } else {
    if (plus_div)
      plus_div.src = "images/plus.gif";
    for (i=1; i < ca_a.length; i++)
    {
      theRow = document.getElementById(ca_a[i])
      theRow.style.display = 'none';
    }
  }
}

To group the CDs shown on our page, the XML markup must be modified to represent the grouping hierarchy. An excerpt from this document is shown in the following listing.

<list.group>
  <list.group.name>WRITER</list.group.name>
  <list.group.description>Song Writer</list.group.description>
  <list.group.value>Harrison</list.group.value>
  <list.children>
    <list.item>
      <list.property>
        <list.property.name>TRACK</list.property.name>
        <list.property.value>1</list.property.value>
      </list.property>
      <list.property>
        <list.property.name>TITLE</list.property.name>
        <list.property.value>Taxman</list.property.value>
        <list.property.href>controller.asp?view=
trackdetail&amp;id=10001</list.property.href>
      </list.property>
      <list.property>
        <list.property.name>LENGTH</list.property.name>
        <list.property.value>3:01</list.property.value>
      </list.property>
      <list.property>
        <list.property.name>WRITER</list.property.name>
        <list.property.value>Harrison</list.property.value>
      </list.property>
    </list.item>
    <list.item>
      <list.property>
        <list.property.name>TRACK</list.property.name>
        <list.property.value>4</list.property.value>
      </list.property>
      <list.property>
        <list.property.name>TITLE</list.property.name>
        <list.property.value>Love You To</list.property.value>
        <list.property.href>controller.asp?view=
trackdetail&amp;id=10004</list.property.href>
      </list.property>
      <list.property>
        <list.property.name>LENGTH</list.property.name>
        <list.property.value>3:01</list.property.value>
      </list.property>
      <list.property>
        <list.property.name>WRITER</list.property.name>
        <list.property.value>Harrison</list.property.value>
      </list.property>
    </list.item>
    <list.item>
      <list.property>
        <list.property.name>TRACK</list.property.name>
        <list.property.value>12</list.property.value>
      </list.property>
      <list.property>
        <list.property.name>TITLE</list.property.name>
        <list.property.value>I Want to Tell You</list.property.value>
        <list.property.href>controller.asp?view=
trackdetail&amp;id=10012</list.property.href>
      </list.property>
      <list.property>
        <list.property.name>LENGTH</list.property.name>
        <list.property.value>2:29</list.property.value>
      </list.property>
      <list.property>
        <list.property.name>WRITER</list.property.name>
        <list.property.value>Harrison</list.property.value>
      </list.property>
    </list.item>
  </list.children>
</list.group>

As you can tell by the following screen shots, a list-based approach works best for laying out multiple items that share similar properties. Lists can also be extended in your application to support user-defined sorting, grouping, filtration, and export. Figure 6-5 shows how an XML list construct is used to display CD tracks. Figure 6-4 shows the CD tracks grouped by author.

Figure 6-4 CD Tracks again, this time grouped by author.


Navigation

All applications, whether they are large enterprise applications or programs for hand-held devices, have a navigation challenge. There are many questions to consider when designing a navigation system. Should the navigation be function based or data based? Should trees be used? Menus? Menus and trees? Toolbars? Buttonbars? The answer to these questions is (of course),"It depends." The navigation tools your application provides and the way in which it organizes them depends on factors such as the application's purpose, breadth, and user base. The bottom line is that the way users navigate through the system is less important than the speed at which they are able to do it.

Although implementations may vary, most navigation systems have similar abstract characteristics. All navigation systems are merely organized collections of links. They might contain links to forms or links to Web pages. They might be organized by the type of function they perform or by the type of data on which they perform it. Therefore, while XML might not be able to tell you how to solve your program's navigation challenge, using it to model your application's navigation scheme can provide several key benefits.

Figure 6-5 Using an XML List construct to display CD Tracks.
  • XML helps you think about navigation in more abstract terms. This frees your mind to think outside the box.
  • It promotes modularity in your application. The business logic that builds the navigation document should not be cluttered with phrases like If browserType = `netscape' then . . .
  • It makes your UI more extensible. Newer navigation UI schemes can be plugged into the existing navigation structure with no modification to your business logic.

The best way to begin thinking about how XML can help is by looking at a sample navigation schema. In this section we will examine how to model a navigation system useing XML. We will then attempt to transform our navigation model into a format suitable for rendering on a Web browser.

Try to think outside the box when incorporating XML into your application. Why not transform an XML navigation tree into a voice-activated UI?

A Simple Navigation Schema

This schema looks similar to the Noverant tree scheme discussed in Chapter 5, and with good reason. A tree is a common navigation component. The heart of the more generic navigation schema is the <navitem> element. Each <navitem> is a link to a document in a Web-based application. A <navitem> contains a description, one or more icons, a hyperlink reference, and a recursive collection of children navitems. This schema is simple yet flexible and offers an infinite variety of ways to model a navigation system.

<ElementType name="navitem.description" content="textOnly" model="closed"/>
<ElementType name="navitem.openIcon" content="textOnly" model="closed"/>
<ElementType name="navitem.closedIcon" content="textOnly" model="closed"/>
<ElementType name="navitem.icon" content="textOnly" model="closed"/>
<ElementType name="href" order="many" content="textOnly" model="closed"/>
<ElementType name="navitem.children" order="many" content="eltOnly" model="closed">
  <element type="navitem" minOccurs="0" maxOccurs="*"/>
</ElementType>
<ElementType name="navitem" order="many" content="eltOnly" model="closed">
  <element type="navitem.description" minOccurs="1" maxOccurs="1"/>
  <element type="navitem.icon" minOccurs="0" maxOccurs="1"/>
  <element type="navitem.openIcon" minOccurs="0" maxOccurs="1"/>
  <element type="navitem.closedIcon" minOccurs="0" maxOccurs="1"/>
  <element type="href" minOccurs="0" maxOccurs="0"/>
  <element type="navitem.children" minOccurs="0" maxOccurs="1"/>
</ElementType>

The <navigation> element is a special <navitem> in that it anchors the navigation hierarchy.

<ElementType name="navigation" order="many" content="eltOnly" model="closed">
  <AttributeType name="type" />
  <element type="navigation.description" minOccurs="1" maxOccurs="1"/>
  <element type="navigation.icon" minOccurs="0" maxOccurs="1"/>
  <element type="navigation.openIcon" minOccurs="0" maxOccurs="1"/>
  <element type="navigation.closedIcon" minOccurs="1" maxOccurs="1"/>
  <element type="href" minOccurs="0" maxOccurs="1"/>
  <element type="navitem.children" minOccurs="0" maxOccurs="1"/>
</ElementType>

A Navigation Example

Now let's put the schema from the previous section to use for Noverant. The following navigation XML document is a snippet from Noverant's back-office application. The example includes entries for maintaining the CD, artist, and inventory databases.

<navigation>
  <description>Noverant Back-Office Root Menu</description>
  <children>
    <navitem>
      <description>Artists</description>
      <icon>artist.gif</icon>
      <children>
        <navitem>
          <description>Search for an Artist</description>
          <href>controller.asp?view=artistSearch</href>
          <icon>artist.gif</icon>
        </navitem>
        <navitem>
          <description>New Artist</description>
          <href>controller.asp?view=artistAdd</href>
          <icon>newArtist.gif</icon>
        </navitem>
      </children>
    </navitem>
    <navitem>
      <description>CDs</description>
      <icon>cd.gif</icon>
      <children>
        <navitem>
          <description>Search for a CD</description>
          <href>controller.asp?view=cdSearch</href>
          <icon>cd.gif</icon>
        </navitem>
        <navitem>
          <description>New CD</description>
          <href>controller.asp?view=cdAdd</href>
          <icon>newCD.gif</icon>
        </navitem>
      </children>
    </navitem>
    <navitem>
      <description>Inventory</description>
      <icon>inventory.gif</icon>
      <children>
        <navitem>
          <description>View Current Inventory</description>
          <href>controller.asp?view=inventory</href>
          <icon>inventory.gif</icon>
        </navitem>
        <navitem>
          <description>View Low-Inventory Items</description>
          <href>controller.asp?view=lowinventory</href>
          <icon>lowinventory.gif</icon>
        </navitem>
        <navitem>
          <description>Place Supply Requisition</description>
          <href>controller.asp?view=requisition</href>
          <icon>requisition.gif</icon>
        </navitem>
      </children>
    </navitem>
  </children>
</navigation>

Note that not all of the <navitem> elements are completely filled out. The code is still valid according to our schema, because only the <description> element is required. Why have a <navitem> element with no hyperlink? The answer is that not all items that appear in a menu system or table-of-contents tree provide links to documents. Some might serve as placeholders to group children elements.

Think about the preceding XML document and what it represents. More importantly, think about what it does not represent. Nothing in the document indicates how the information should be rendered. The navigation elements can be displayed as a tree, drop-down menu, or tab menu just as easily. Let's explore this example further and create two different XSLT implementations for our navigation document.

Creating a Tab Menu

The first implementation will be a two-tiered tab approach. The presentation rules will assume a two-level navigation document (that is, a list of categories and options) and will build a set of tabs that, when clicked, reveal a list of options from which the user can choose. The following XSLT code will be added to the other template rules we have defined thus far.

<xsl:template match="navigation[@type='menu']">
  <html>
    <head>
      <xsl:text disable-output-escaping = "yes">
        <![CDATA[
          <link rel="stylesheet" type="text/css" href="css/style.css"/>
        ]]>
      </xsl:text>
      <style type="text/css">
        body{background-color:C0C0C0}
      </style>
      <script type="text/javascript">

The following JavaScript functions handle all user interaction with the tab folders. The clearFunctions( ) function clears out the present set of options whenever a new tab is clicked. The clearModuleStyles( ) deemphasizes the inactive tabs. Finally the showFunction( ) function highlights the chosen tab and builds a new list of options.

      <![CDATA[
        function clearFunctions() {
          var i = 1;
          var theFunction = "";
          var isFunction =
          true;
          while (isFunction) {
            theFunction = "function"+i;
            var hideDiv = document.getElementById(theFunction);
            if (hideDiv) {
              hideDiv.style.visibility = "hidden";
              i++;
            } else {
              isFunction = false;
            }
          }
        }
        function clearModuleStyles() {
          var i = 1;
          var theModule = "";
          var isModule = true;
          while (isModule) {
            theModule = "module"+i;
            theModuleLeft = "moduleLeft"+i;
            theModuleRight = "moduleRight"+i;
            theModuleAnchor = "moduleAnchor"+i;
            cleanStyle = document.getElementById(theModule);
            cleanStyleLeft = document.getElementById(theModuleLeft);
            cleanStyleRight = document.getElementById(theModuleRight);
            cleanStyleAnchor = document.getElementById(theModuleAnchor);
            if (cleanStyle) {
              cleanStyle.style.backgroundColor = 'steelblue';
              cleanStyle.style.borderBottom = '1px solid';
              cleanStyle.style.borderColor = '336699';
              cleanStyleLeft.style.backgroundColor = 'steelblue';
              cleanStyleLeft.style.borderBottom = '1px solid';
              cleanStyleLeft.style.borderColor = '336699';
              cleanStyleRight.style.backgroundColor = 'steelblue';
              cleanStyleRight.style.borderBottom = '1px solid';
              cleanStyleRight.style.borderColor = '336699';
              cleanStyleAnchor.style.color = 'white';
              i++;
            } else {
              isModule = false;
            }
          }
        }
        function showFunction(id) {
          clearFunctions();
          clearModuleStyles();
          theId = "function"+id;
          thisFunction = document.getElementById(theId);
          thisFunction.style.visibility = 'visible';
          theId = "module"+id;
          theLeftId = "moduleLeft"+id;
          theRightId = "moduleRight"+id;
          theAnchorId = "moduleAnchor"+id;
          thisModule = document.getElementById(theId);
          thisModuleRight = document.getElementById(theRightId);
          thisModuleLeft = document.getElementById(theLeftId);
          thisModuleAnchor = document.getElementById(theAnchorId);
          thisModule.style.backgroundColor = 'white';
          thisModule.style.borderBottom = '0';
          thisModuleRight.style.backgroundColor = 'white';
          thisModuleRight.style.borderBottom = '0';
          thisModuleLeft.style.backgroundColor = 'white';
          thisModuleLeft.style.borderBottom = '0';
          thisModuleAnchor.style.color = '336699';
        }
        window.status = "Noverant Back-Office";
      ]]>
      </script>
    </head>

This transformation assumes that the navigation document will exist in its own frame, as it creates a complete HTML document. The template can easily be modified to share a frame with other UI elements.

In the following section of code the template iterates over the top-level <navitem> elements and creates tab folders for each of them. JavaScript methods are attached to onClick( ), onMouseOver( ), and onMouseOut( ) event handlers.

    <body marginwidth="0" marginheight="0" leftmargin="0" topmargin="0">
      <div id="nav1" style="display:inline">
        <table cellspacing="0" cellpadding="0" border="0">
        <tr>
          <xsl:for-each select="children/navitem">
            <td height="25" valign="top" id="moduleLeft{position()}">
              <xsl:choose>
                <xsl:when test="position() = 1">
                  <xsl:attribute name="style">background-color:white
</xsl:attribute>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:attribute name="style">background-color:
steelblue;border-bottom:1px solid;border-color:336699</xsl:attribute>
                </xsl:otherwise>
              </xsl:choose>
              <img src="images/left.gif" width="4" height="4"/>
            </td>
            <td align="center" height="25" id="module{position()}"
nowrap="true">
              <xsl:choose>
                <xsl:when test="position() = 1">
                  <xsl:attribute name="style">padding:2px;
background-color:white</xsl:attribute>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:attribute name="style">padding:2px;
background-color:steelblue;border-bottom:1pxsolid;
border-color:336699
                  </xsl:attribute>
                </xsl:otherwise>
              </xsl:choose>
              <a class="navigation" href="javascript:
showFunction('{position()}');" id="moduleAnchor{position()}">
              <xsl:attribute name="onMouseOver">JavaScript:window.
status='<xsl:value-of select="description"/>'; return true;
</xsl:attribute>
              <xsl:attribute name="onMouseOut">JavaScript:
window.status='Noverant Back-Office'; return true;</xsl:attribute>
              <xsl:if test="position() = 1">
                <xsl:attribute name="style">color:336699
</xsl:attribute>
              </xsl:if>
              <xsl:value-of select="description"/></a>
            </td>
            <td height="25" valign="top" id="moduleRight{position()}">
              <xsl:choose>
                <xsl:when test="position() = 1">
                  <xsl:attribute name="style">background-color:white
</xsl:attribute>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:attribute name="style">background-color:
steelblue;border-bottom:1px solid;border-color:336699</xsl:attribute>
                </xsl:otherwise>
              </xsl:choose>
              <img src="images/right.gif" width="4" height="4"/>
            </td>
            <td width="1" bgcolor="C0C0C0"><img src="images/spacer.gif"
width="1" height="25"/></td>
          </xsl:for-each>
        </tr>
        </table>
      </div>
The XSL generate-id( ) function is invaluable for creating unique IDs to attach to elements used in your output document. The generate-id( ) guarantees that it will produce a unique and repeatable ID for every node in the source document.

At this point the template iterates over all of the second-level <navitem> elements and creates a list of hyperlinks out of them. These hyperlinks will appear below the tab folders.

      <xsl:for-each select="children/navitem">
        <xsl:variable name="i" select="position()"/>
        <div id="function{position()}">
          <xsl:choose>
            <xsl:when test="position() != 1">
              <xsl:attribute name="style">position:absolute;
display:inline;visibility:hidden;width:100%;background-color:white;
border-bottom:1px solid;border-color:steelblue</xsl:attribute>
            </xsl:when>
            <xsl:otherwise>
              <xsl:attribute name="style">position:absolute;
display:inline;width:100%;background-color:white;border-bottom:1px
solid;border-color:steelblue</xsl:attribute>
            </xsl:otherwise>
          </xsl:choose>
          <table cellspacing="0" cellpadding="0" border="0" style=
"padding:2px">
          <tr>
            <td style="color:336699" class="text">
              <xsl:attribute name="nowrap"/>
              <xsl:for-each select="children/navitem">
                <xsl:variable name="j" select="position()"/>
                <xsl:variable name="functionVar" select=
"concat($i,'_',$j)"/>
                  &#160;
                  <a class="subNavigation" name="{generate-id()}"
href="controller.asp?view={href}" target="main">
                    <xsl:attribute name="onMouseOver">JavaScript:
window.status='<xsl:value-of select="description"/>'; return true;
</xsl:attribute>
                    <xsl:attribute name="onMouseOut">JavaScript:
window.status='Noverant Back-Office'; return true;</xsl:attribute>
                    <xsl:value-of select="description"/>
                  </a>
                  &#160;
                <xsl:if test="position() != last()">&#160;|&#160;
</xsl:if>
              </xsl:for-each>
            </td>
          </tr>
          </table>
        </div>
      </xsl:for-each>
    </body>
  </html>
</xsl:template>

A call to this page from frameset will produce the page in Figure 6-6.

Figure 6-6 A tab-folder approach to navigation.

Creating a Navigation Tree

For the second implementation, the same navigation XML can be used to create a tree-based navigation system. By rewriting the navigation XSLT, a page designer can create a drastically different front-end. This example of an XSLT used to produce a tree demonstrates the flexibility that can be achieved by separating content from presentation.

Like the menu transformation, the tree transformation assumes that the resulting document will reside in its own frame.

<xsl:template match="navigation[@type='tree']">
  <html>
    <head>
      <script language="JavaScript">
        function expand(divid)
        {
           eval("closed_"+divid).style.display = 'none';
           eval("open_"+divid).style.display = '';
        }
        function collapse(divid)
        {
           eval("open_"+divid).style.display = 'none';
           eval("closed_"+divid).style.display = '';
        }
      </script>
      <style>
        a:visited {background-color:#C7D2DE; color:black;
text-decoration: underline}
        a:link {background-color:#C7D2DE; color:black; text-decoration: underline}
        a:active {background-color:activecaption; color:captiontext; text-decoration: underline}
        a:hover {background-color:#C7D2DE; color:black;
text-decoration: underline}
        .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;}
        .clsEntryText {padding-top: 2px; font-family: verdana; color: black;
font-size: 11; font-weight: 400; background-color:#C7D2DE;}
        .clsWarningText {font-family: verdana; white-space:pre; color: #B80A2D;
font-size: 11; font-weight: 600; width:550;
background-color:#EFE7EA;}
        .clsCopy {font-family: verdana; color: black; font-size: 11;
font-weight: 400;  background-color:#FFFFFF;}
      </style>
    </head>
    <body style="background-color:#C7D2DE;">
      <table width="100%" cellspacing="5">
        <tr>
          <td nowrap="yes">
            <div class="clsEntryText" style="padding-left:5px" id="open_{generate-id()}">
              <xsl:if test="openIcon"><img src="images/{openIcon}"/>
&#160;</xsl:if>
              <xsl:if test="href"><a href="{href}">
                <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
                <xsl:value-of select="description"/></a>
              </xsl:if>
              <xsl:if test="not(href)"><xsl:value-of select=
"description"/></xsl:if>
              <xsl:apply-templates select="children"/>
            </div>
          </td>
        </tr>
      </table>
    </body>
  </html>
</xsl:template>

The recursive nature of the navigation tree schema suits XSLT perfectly. <navitem> elements can be transformed in place with little conditional logic.

<xsl:template match="navitem">
  <xsl:choose>
    <xsl:when test="children">
      <xsl:variable name="childcnt" select="count(.//navitem)"/>
      <div class="clsEntryText" id="closed_{generate-id()}">
        <xsl:for-each select="ancestor::navitem">
          <xsl:choose>
            <xsl:when test="count(following-sibling::navitem) > 0">
              <img src="/xmlbook/images/line.gif"/>
            </xsl:when>
            <xsl:otherwise>
              <img src="/xmlbook/images/nothing.gif"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
        <img src="/xmlbook/images/folder_plus.gif" style="cursor:hand"
onClick="expand('{generate-id()}')"/>
        <xsl:choose>
          <xsl:when test="closedIcon"><img src="images/{closedIcon}"
/>&#160;</xsl:when>
          <xsl:when test="icon"><img src="images/{icon}"/>&#160;
</xsl:when>
        </xsl:choose>
        <xsl:if test="href"><a href="{href}">
          <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
          <xsl:value-of select="description"/></a>
        </xsl:if>
        <xsl:if test="not(href)"><xsl:value-of select="description"/>
</xsl:if>
        (<xsl:value-of select="$childcnt"/> artist<xsl:if test=
"$childcnt != 1">s</xsl:if>)
      </div>
      <div class="clsEntryText" id="open_{generate-id()}" style=
"display:none">
        <xsl:for-each select="ancestor::navitem">
          <xsl:choose>
            <xsl:when test="count(following-sibling::navitem) > 0">
              <img src="/xmlbook/images/line.gif"/>
            </xsl:when>
            <xsl:otherwise>
              <img src="/xmlbook/images/nothing.gif"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
        <img  src="/xmlbook/images/folder_minus.gif" style=
"cursor:hand" onClick="collapse('{generate-id()}')"/>
        <xsl:choose>
          <xsl:when test="openIcon"><img src="images/{openIcon}"
/>&#160;</xsl:when>
          <xsl:when test="icon"><img src="images/{icon}"/>&#160;
</xsl:when>
        </xsl:choose>
        <xsl:if test="href"><a href="{href}">
          <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
          <xsl:value-of select="description"/></a>
        </xsl:if>
        <xsl:if test="not(href)"><xsl:value-of select="description"/>
</xsl:if>
         (<xsl:value-of select="$childcnt"/> artist<xsl:
if test="$childcnt != 1">s</xsl:if>)
        <xsl:apply-templates select="children"/>
      </div>
    </xsl:when>
    <xsl:otherwise>
      <div class="clsEntryText">
        <xsl:for-each select="ancestor::navitem">
          <xsl:choose>
            <xsl:when test="count(following-sibling::navitem) > 0">
              <img src="/xmlbook/images/line.gif"/>
            </xsl:when>
            <xsl:otherwise>
              <img src="/xmlbook/images/nothing.gif"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
        <img  src="/xmlbook/images/divider.gif" onClick=
"collapse('{generate-id()}')"/>
        <xsl:if test="icon"><img src="images/{icon}"/>&#160;</xsl:if>
        <xsl:if test="href"><a href="{href}">
          <xsl:if test="href/@target"><xsl:attribute name=
"target"><xsl:value-of select="href/@target"/></xsl:attribute>
</xsl:if>
          <xsl:value-of select="description"/></a>
        </xsl:if>
        <xsl:if test="not(href)"><xsl:value-of select="description"/>
</xsl:if>
      </div>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Figure 6-7 shows what the Noverant back-office application looks like using this transformation.

Figure 6-7 A tree-based navigation system.

An XML-based navigation system is inherently extensible. Its presentation rules can be changed in a variety of ways to dramatic effect. Another alternative to the two examples shown here is to develop a transformation that creates drop-down menus. This exercise, however, will be left to the reader.


Input Controls

Input widgets such as buttons, combo boxes, and text fields are essential to UIs. We couldn't do much without them! All development environments provide their own sets of widgets. The HTML specification includes support for many of these controls. Unfortunately, the widget set that HTML provides is not nearly as rich as those found in thick-client applications.

Fortunately (or unfortunately, as the case may be), browser vendors have ventured beyond the W3C specification in their support for UI controls. Microsoft has added support for drag and drop, "What-You-See-Is-What-You-Get" (WYSIWYG) editing, right-click context menus and a host of other enhancements to its Internet Explorer browser. Netscape has embarked on an XML UI project of its own, called XML User-Interface Language (XUL). XUL was the toolset used to create the Mozilla browser's UI. Hooks to these widgets were added to the browser's API to allow developers to take advantage of them.

While these enhancements certainly allow for a richer browser UI, they can be confusing, if not problematic, to the application developer trying to sort through them. With all these toolsets available, does it make sense to roll your own? Perhaps. This section will explore the benefits of creating your own UI schema and will describe XML models and XSLTs for simple HTML form input controls. We will also learn how to extend these controls to take advantage of some browser-specific features.

Consistency

The internal adoption of your own XML schema to UI control design will enforce visual consistency within your application. For example, select boxes, radio buttons, and check boxes are often used interchangeably. How many times have you seen a Web page that looks like the one in Figure 6-8?

Figure 6-8 Why do these questions need to use different input controls?

All three questions in this page limit the user to binary responses. However, page designers, when left alone, will often mix and match these controls. Sometimes they even do it intentionally! How many times have you thought "I haven't used a check box control in several pages. Why don't I throw one in here"?

XML will rid your application of the "Let's be kind to the check box" syndrome. Imagine programming to a more generic interface when gathering input. For example, the following XML could have been used to clean up the page in Figure 6-8.

<property description="What is your gender?">
<choice name="gender" allowmultiple="no">
<option value="F">Female</option>
<option value="M">Male</option>
</choice>
</property>
<property description="Are you a vegetarian?">
<choice name="vegie" allowmultiple="no">
  <option value="Y">Yes</option>
  <option value="N">No</option>
</choice>
</property>
<property description="Which of these vegetables do you like?">
<choice name="favoriteFoods" allowmultiple="yes">
  <option value="turnips">Turnips</option>
  <option value="asparagus">Asparagus</option>
  <option value="carrots">Carrots</option>
  <option value="celery">Celery</option>
</choice>
</property>

The XSLT rules will govern what input control to use in a given context. The rule might be "Check boxes for two or more items, select boxes for all others," or it might be "Radio buttons all the time." Regardless, the UI will be consistent. This approach makes everyone's life easier. The page developer needs only remember a single API, and users doesn't need to suffer through three different ways of answering a "Yes/No" question.

You might also note in the previous example that the input fields are encapsulated by a property element. As we saw in previous examples, the property element makes the page developer's life much easier. Nearly every HTML form you encounter is a table with one column of prompts and another column of input controls. By including the prompt in the XML schema you save the developer from having to build new table rows and table cells for each question. The developer doesn't need to worry about the positioning of attributes or any other layout issues either. These are resolved by the XSLT layout manager.

The <form> Element

The first element we need to add to our schema is the <form> element, to indicate the presence of an input form. This element will look like the HTML <form> element with which you are already familiar. It will have only one essential attribute, the action attribute. Here is an example:

<form action="controller.asp?view=processresults"/>

This element, when transformed, will produce an HTML form that submits its contents to a Web page called controller.asp. Our first attempt to transform this element will be relatively straightforward.

<xsl:template match="form">
  <form action="{@action}" method="post">
    <xsl:for-each select="@*[name() != 'action']">
      <xsl:copy><xsl:value-of select="."/></xsl:copy>
    </xsl:for-each>
    <xsl:apply-templates/>
    &#160;&#160;<input class="text" type="submit"/>
  </form>
</xsl:template>

Now that we have a form, we need something to put in it. We will need to define wrapper elements for each of the HTML form controls we will implement.

Use the "{expression}" shortcut when embedding XPath expressions inside element attributes. This is much less cumbersome than the <xsl:attribute> element.

The Text <input> Element

The first control we will implement is the standard text input control. This element will also be a subset of the HTML control. The element will be wrapped by a <property> element to control layout and formatting. An example of this element follows.

<property description="Enter the CD's description">
  <input type="text" size="30" value="Revolver" name="description"/>
</property>

The transformation for this element is simple. Notice that this transformation, like the form transformation before, iterates over the <input> element's attribute list and propagates them through to the output document. This open approach to the schema allows the page developer to pass through other attributes that the XSLT doesn't need to worry about, such as event handlers, to the result document.

<xsl:template match="input[@type='text']">
  <input type="text" name="{@name}" value="{@value}" class="text" >
    <xsl:for-each select="@*[name() != 'name' and name() != `value']">
      <xsl:copy><xsl:value-of select="."/></xsl:copy>
    </xsl:for-each>
  </input>
</xsl:template>

The controls we have defined thus far provide enough ammunition to build a simple example. The example will be a CD input page that Noverant employees can use to edit albums in their inventory. The example in Figure 6-9 shows the new form controls.

Figure 6-9 A simple CD edit example showing off new form controls.
<document>
  <header>
    <title>The Beatles: Revolver</title>
    <icon>CD.gif</icon>
  </header>
  <form action="cdinput.asp">
    <section>
      <header>
        <title>General Information</title>
        <icon>world.gif</icon>
      </header>
      <view>
        <properties>
          <property description="Artist">
            <input type="text" name="artist" value="The Beatles"/>
          </property>
          <property description="Album">
            <input type="text" name="description" value="Revolver"/>
          </property>
          <property description="Label">
            <input type="text" name="label" value="EMI Records Ltd"/>
          </property>
          <property description="Year Released">
            <input type="text" name="releaseDate" value="1966"/>
          </property>
          <property description="Price">
            <input type="text" name="price" value="13.99"/>
          </property>
        </properties>
      </view>
    </section>
  </form>
</document>

The TextArea <input> Element

TextAreas are input controls familiar to Web form developers. They are more cumbersome to use than text input tags but are still necessary to capture longer streams of unstructured data. Our XML implementation will modify this element in three ways.

  • It will alter its syntax slightly, making it more consistent with regular text input tags. The HTML 4.01 and XHTML 1.1 specifications defined the textarea syntax to be <textarea>. . .</textarea>. This implementation will change the usage to look like <input type="textarea">. . .</input> for simplicity's sake. Changing the syntax also helps the content developer remember that he or she is programming to a schema that you have defined.
  • It will provide broad height and width defaults so that content developers do not need to worry about them. This will help make the application UI more consistent.
  • It will provide support for WYSIWYG editing to those users who have Internet Explorer 5.5 or later. This is a fantastic feature that opens up drag-and-drop and rich-text formatting to Web users.

The syntax for the textarea <input> element is exactly like its simple text counterpart, with the exception of a single attribute. Figure 6-10 shows an implementation of the textarea control.

Figure 6-10 An implementation of the textarea control.

Here is an example of its use:

<property description="Enter the CD's description">
  <input type="textarea" value="" name="notes"/>
</property>

The XSLT template to build an HTML textarea is as follows:

<xsl:template match="input[@type='textarea']">
  <textarea class="text" wrap="{@word_wrap}" name="{@name}">
    <xsl:choose>
      <xsl:when test="@rows!=''">
        <xsl:attribute name="rows"><xsl:value-of select="@rows"/>
</xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
        <xsl:attribute name="rows">8</xsl:attribute>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:choose>
      <xsl:when test="@cols!=''">
        <xsl:attribute name="cols"><xsl:value-of select="@cols"/>
</xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
        <xsl:attribute name="cols">56</xsl:attribute>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="@value"/>
  </textarea>
  <xsl:if test="@mandatory = 'yes'">
    <font class="error">&#160;*</font>
  </xsl:if>
</xsl:template>.

Field Validation

Almost all applications need to validate the data that a user has entered before saving it. Some fields might be mandatory, while others, such as numeric fields, expect their values in a certain format. XML can help developers implement client-side form validation with little pain. All that is necessary is to add two new attributes, mandatory and datatype, to the <input> element and to update the XSLT.

For example, the "price" field on the CD edit form example should be mandatory and numeric. The new input field will look like this under the new schema:

<property description="Price">
<input type="text" name="price" value="13.99" mandatory="yes" datatype="numeric"/>
</property>
The W3C's XForms specification also seeks to solve the client-side data validation problem. See this specification for more interesting ideas on abstract forms. It can be found at http://www.w3.org/MarkUp/Forms/ .

Nothing more is required of the page developer to indicate restrictions on a particular input field. The bulk of the validation work will be done by the XSLT. It will be modified to include two new functions: hasValue(obj, obj_type) and isOfType(val, data_type). These scripts will be placed in a separate .js file that is included by the document template. The contents of Listing 6-6 follow.

Listing 6-6 formValidate.js: Contains the functions used for client-side mandatory field checking and datatype validation.

function hasValue(obj, obj_type)
{
    if (obj_type == "text" || obj_type == "password")
  {
      if (obj.value.length == 0)
          return false;
      else
          return true;
      }
    else if (obj_type == "select")
  {
        for (i=0; i < obj.length; i++)
        {
    if (obj.options[i].selected)
      return true;
    }
         return false;
  }
    else if (obj_type == "single_radio" || obj_type == "single_checkbox")
  {
    if (obj.checked)
      return true;
    else
           return false;
  }
    else if (obj_type == "radio" || obj_type == "checkbox")
  {
        for (i=0; i < obj.length; i++)
        {
    if (obj[i].checked)
      return true;
    }
         return false;
  }
}
function isoftype(the_val, data_type)
{
  if (data_type == "date")
  {
    if (isNaN(Date.parse(the_val)))
    {
      return false;
    } else {
      return true;
    }
  }
  if (data_type == "time")
  {
    if (isNaN(Date.parse("01/01/1970 "+the_val)))
    {
      return false;
    } else {
      return true;
    }
  }
  if (data_type == "monetary" || data_type == "numeric")
  {
    if (isNaN(the_val))
    {
      return false;
    } else {
      return true;
    }
  }
}

The XSLT must be altered both to include this JavaScript file and to change the form submission process to check for mandatory and restricted fields. First the following line needs to be added to the head of the result document:

<script language="javaScript" src="js/formvalidate.js"/>

Second the XSLT form template must be modified to build a dynamic JavaScript function that validates its contents. This function will be called during the form's onSubmit( ) event.

Because each validateForm( ) function is unique to the contents of a particular form, its name must be unique to prevent collisions with other forms that might exist on the same page. The XSL function generate-id( ) is used for this purpose.

<xsl:template match="form">
  <script language="JavaScript">
    function validateForm_<xsl:value-of select="generate-id()"/>
(thisForm)
    {

The first part of the function iterates over all input elements in the XML document with the mandatory attribute set to "yes". The function then determines, for each mandatory field, the type of input control (for example, text box, textarea, and so forth), and then makes an appropriate call to the hasValue( ) function. If hasValue( ) returns false, the validateForm( ) function displays an error message, attempts to set the page focus to the field, and aborts the form submission.

      <xsl:for-each select="//*[@mandatory='yes']">
        <xsl:choose>
          <xsl:when test="@type = 'text' or @type='textarea'">
            if (!hasValue(thisForm.<xsl:value-of select="@name"/>
,"text"))
            {
              alert("<xsl:value-of select="@name"/> is mandatory.");
              var the_field = thisForm.<xsl:value-of select="@name"/>;
              if(the_field.style.display != "none" &amp; !the_field.disabled &amp; !the_field.readOnly &amp;
 !the_field.editableDiv)
                the_field.focus();
              return false;
            }
          </xsl:when>
        </xsl:choose>
      </xsl:for-each>

Next the function needs to check any datatype restrictions mentioned in the XML source. This algorithm is similar to the one used to perform mandatory value checking. The script iterates over all input controls with a datatype attribute specified. For each attribute the script makes a call to the isoftype( ) function to match the format of the data entered by the user to the required format. If the isoftype( ) function call returns false, the validateForm( ) script reports the error to the user and aborts the form submission.

      <xsl:for-each select="//input[@datatype]">
        if (!isoftype(thisForm.<xsl:value-of select="@name"
/>.value,'<xsl:value-of select="@datatype"/>'))
        {
          alert("<xsl:value-of select="@name"/> is not in <xsl:value-of select="@datatype"/> format");
          return false;
        }
      </xsl:for-each>

Finally the validateForm( ) function must inspect the form's onSubmit( ) event handler to see if further post processing is required. If the page developer specified an onSubmit( ) function to be called, the validateForm( ) function will call it before completing.

      <xsl:choose>
        <xsl:when test="@onSubmit">
          var rtnval = <xsl:value-of select="@onSubmit"/>;
          return rtnval;
        </xsl:when>
        <xsl:otherwise>
          return true;
        </xsl:otherwise>
      </xsl:choose>
    }
  </script>

The <form> HTML element must now be altered by the template to call the new formValidate( ) function when it is submitted, which is achieved by setting its onSubmit attribute.

  <form action="{@action}" method="post" onSubmit="return
validateForm_{generate-id()}(this)">
    <xsl:for-each select="@*[name() != 'action']">
      <xsl:copy><xsl:value-of select="."/></xsl:copy>
    </xsl:for-each>
    <xsl:apply-templates/>
    &#160;&#160;<input class="text" type="submit"/>
  </form>
</xsl:template>

WYSIWYG Editing

With the release of Internet Explorer 5.5, Microsoft made the browser's WYSIWYG HTML editing control available for use in Web pages. This control provides many useful options, such as:

  • Cut and Paste to and from other Windows applications, such as Microsoft Word
  • Embedded images
  • Rich-text formatting including bold, italics, underline, left, center, and right justification
  • User-defined hyperlinks
  • Text range formatting
  • Undo/Redo functionality

Developers can use this feature by setting a division's contentEditable attribute. Unfortunately, Internet Explorer does not implement this attribute on standard HTML input controls. For example, <textarea contentEditable = "true".../> will not have the desired effect. Instead, we must accomplish our goal through a bit of trickery. The good news is that the XSLT will handle all of the dirty work for us without any change to our forms XML object model. This is an excellent illustration of how XML and XSLT facilitate application extensibility.

The Microsoft WYSIWYG editing control does a remarkable job of creating well-formed HTML code. Well-formed HTML is much easier to incorporate into XML documents.

The trick to adding WYSIWYG editing to a text box is not to use a text box at all. We will use a combination of divisions and hidden variables to pull this off. To begin, the textarea XSLT template will change to insert a <div> element instead of a <TEXTAREA> element into the result document. The <div> element will have its contentEditable attribute set to "true" to enable WYSIWYG editing. You might also note that the template has added support for a new size attribute in the textarea <input> element. This attribute accepts values of "small", "medium", and "large" so that content developers can control the relative size of the input box without worrying about specifying an exact pixel height and width.

<xsl:template match="input[@type='textarea']">
  <div id="d{generate-id()}" contentEditable="true" class="text">
    <xsl:choose>
      <xsl:when test="@size='small'"><xsl:attribute name="style">
display:inline;overflow:scroll;width=350;border:solid;
border-style:ridge;border-width:2;background-color:white;height:150
</xsl:attribute></xsl:when>
      <xsl:when test="@size='medium'"><xsl:attribute name="style">
display:inline;overflow:scroll;width=350;border:solid;
border-style:ridge;border-width:2;background-color:white;height:250
</xsl:attribute></xsl:when>
      <xsl:when test="@size='large'"><xsl:attribute name="style">
display:inline;overflow:scroll;width=350;border:solid;
border-style:ridge;border-width:2;background-color:white;height:350
</xsl:attribute></xsl:when>
      <xsl:otherwise><xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:350</xsl:attribute>
</xsl:otherwise>
    </xsl:choose>
    <xsl:value-of disable-output-escaping="yes" select="value"/>
  </div>
  <input type="hidden" editableDiv="yes" name="{@name}" value="{@value}"/>
  <xsl:if test="@mandatory = 'yes'">
    <div style="display:inline"><font class="error">&#160;*</font>
</div>
  </xsl:if>
</xsl:template>

This code will insert a WYSIWYG-editable division into the Web page. How do we move the contents of this division into a form variable to be submitted with the form? The answer lies in the form validation function. We need to insert logic into this function that will create hidden form variables with the same names as the textareas to be submitted with the form.

function validateForm_<xsl:value-of select="generate-id()"/>(thisForm)
{
  <xsl:for-each select="//input[@type='textarea']">
thisForm.<xsl:value-of select="@name"/>.value =
document.getElementById('d<xsl:value-of select="generate-id()"
/>').innerHTML;
  </xsl:for-each>
  // rest of the validateForm() function goes here
}

Now, like magic, users can edit your textareas just as if they were editing a Word document. Figure 6-11 shows the XML document with WYSIWYG editing enabled.

Dynamic XSL Transformations

WYSIWYG editing is fantastic for users with capable browsers, but what about support for older or nonconformant browsers? Our current textarea transformation would produce a division that could not be edited by any browser that does not support WYSIWYG (for example, Internet Explorer 5.0 or later). Certainly, this is a problem.

One solution might be to create a second textarea <input> XML element that looked like <input type="richtextarea" ...>. The ASP page developer could check the user's browser at runtime and use the plain textarea <input> element to support older versions. While this scheme might work, it casts the burden of worrying about presentation onto the page developer. This is contrary to XML's philosophy of separating content from presentation and should be avoided if possible.

Figure 6-11 Same XML document as in Figure 6-10, with WYSIWYG editing enabled.

Another solution might be to create multiple XSLT templates, one for each class of Web browser. The Internet Explorer 5.0 template would use the regular textarea control while the Internet Explorer 5.5 and later templates would take advantage of the rich-text version. The page controller would determine the user's browser and select the correct template for rendering the page results. This approach has merits because it removes the presentation responsibility from the page developer, which is good. The approach is also extensible. Whenever a new browser hits the market, the XSLT designer need only create a new transformation geared to the new software and drop it into the XSLT library.

The downside to this solution is that it yields a proliferation of slightly different skins, all of which must be maintained. This situation becomes particularly problematic when changes are introduced into the application's UI schema. All of the transformations must be updated whenever this happens. While a transformation skin library might be useful for separating wildly different classes of browsers (for example, separating Netscape 4.71 from Internet Explorer 6.0), it behooves the XSLT developer to keep the number of transformations in this library under control.

Instead of creating a new skin for each Web browser, the XSLT developer can make use of another technique, known as "dynamic XSLT," to manage subtle differences between browsers. Dynamic XSLT is simple to explain and understand although its application is limitless (and sometimes mind-bending!). The idea is simple: make your XSLTs ASPs. No rule says XSLT pages need to be static. The plain vs. rich text textarea problem can be solved by a single ASP transformation. The logic in the page might look something like the following:

<%
If browser = "MSIE" and broserVersion >= 5.5 Then
%>
<xsl:template match="input[@type='textarea']">
  <div id="d{generate-id()}" contentEditable="true" class="text">
    <xsl:choose>
      <xsl:when test="@size='small'"><xsl:attribute name="style">
display:inline;overflow:scroll;width=350;border:solid;
border-style:ridge;border-width:2;background-color:white;height:150
</xsl:attribute></xsl:when>
      <xsl:when test="@size='medium'"><xsl:attribute name="style">
display:inline;overflow:scroll;width=350;border:solid;
border-style:ridge;border-width:2;background-color:white;height:250
</xsl:attribute></xsl:when>
      <xsl:when test="@size='large'"><xsl:attribute name="style">
display:inline;overflow:scroll;width=350;border:solid;
border-style:ridge;border-width:2;background-color:white;height:350
</xsl:attribute></xsl:when>
      <xsl:otherwise><xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:350</xsl:attribute>
</xsl:otherwise>
    </xsl:choose>
    <xsl:value-of disable-output-escaping="yes" select="value"/>
  </div>
  <input type="hidden" editableDiv="yes" name="{@name}" value="{@value}"/>
  <xsl:if test="@mandatory = 'yes'">
    <div style="display:inline"><font class="error">&#160;*</font>
</div>
  </xsl:if>
</xsl:template>
<%
End Else
%>
<xsl:template match="input[@type='textarea']">
  <textarea class="text" wrap="{@word_wrap}" name="{@name}">
    <xsl:choose>
      <xsl:when test="@rows!=''">
        <xsl:attribute name="rows"><xsl:value-of select="@rows"/>
</xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
        <xsl:attribute name="rows">8</xsl:attribute>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:choose>
      <xsl:when test="@cols!=''">
        <xsl:attribute name="cols"><xsl:value-of select="@cols"/>
</xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
        <xsl:attribute name="cols">56</xsl:attribute>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="@value"/>
  </textarea>
  <xsl:if test="@mandatory = 'yes'">
    <font class="error">&#160;*</font>
  </xsl:if>
</xsl:template>
<%
  End
%>
XSLT documents are well-formed XML and can be transformed by XSLT. Imagine using XSLT to transform another XSLT document to transform a source XML document. Not only is this possible, it is often good design.

Dynamic XSLT offers an ideal solution to tricky differences between otherwise similar browsers. Again, a skin library should be used to isolate presentation rules across different browser classes. However, this technique is very useful, particularly as newer browsers approach the panacea of full W3C specification support.

The <choice> Element

The <choice> element is used to automate the creation of HTML select boxes, radio buttons, and check boxes. Let's re-examine the example supplied at the beginning of the section.

<property description="What is your gender?">
<choice name="gender" allowmultiple="no">
<option value="F">Female</option>
<option value="M">Male</option>
</choice>
</property>
<property description="Are you a vegetarian?">
<choice name="vegie" allowmultiple="no">
  <option value="Y">Yes</option>
  <option value="N">No</option>
</choice>
</property>
<property description="Which of these vegetables do you like?">
<choice name="favoriteFoods" allowmultiple="yes">
  <option value="turnips">Turnips</option>
  <option value="asparagus">Asparagus</option>
  <option value="carrots">Carrots</option>
  <option value="celery">Celery</option>
</choice>
</property>

The XSLT for this control should incorporate presentation rules to determine how the choices should be rendered. The extreme cases are easy to Figure out. A choice with two hundred options is probably best displayed inside a <select> combo box. At the other end of the spectrum a choice offering two options with the allowmultiple attribute turned on is best shown as two check boxes. Our transformation will use five options as an arbitrary cutoff between select boxes and check boxes/radio buttons. Your presentation rules might differ or change over time.

In the transformation you will first notice the test of the option count. If the count is greater than five the template creates a select box, setting its multiple and size attributes to yes and 5, respectively, if the user is allowed to select more than one option. In the case of small choices, where the option count is at most five, the template creates either a series of check boxes or a series of radio buttons with the same name. Check boxes are used when the allowmultple attribute is set to yes; radio buttons are used when it is not.

<xsl:template match="choice">
  <xsl:choose>
    <xsl:when test="count(option) > 5">
      <select class="text">
        <xsl:for-each select="@*[name() != 'mandatory']">
          <xsl:copy><xsl:value-of select="."/></xsl:copy>
        </xsl:for-each>
        <xsl:if test="@allowmultiple = 'yes'">
          <xsl:attribute name="multiple">yes</xsl:attribute>
          <xsl:attribute name="size">5</xsl:attribute>
        </xsl:if>
        <option value="">Please Choose Below</option>
        <xsl:for-each select="option">
          <option>
            <xsl:attribute name="value"><xsl:value-of select="@value"/>
</xsl:attribute>
            <xsl:if test="@selected='yes'">
              <xsl:attribute name="selected"><xsl:value-of select="@selected"/></xsl:attribute>
            </xsl:if>
            <xsl:value-of select="."/>
          </option>
        </xsl:for-each>
      </select>
    </xsl:when>
    <xsl:otherwise>
      <div style="display:inline">
        <xsl:for-each select="option">
          <xsl:choose>
            <xsl:when test="../@allowmultiple = 'yes'">
              <input type="checkbox" name="{../@name}"
value="{@value}"/>&#160;<xsl:value-of select="."/><br/>
            </xsl:when>
            <xsl:otherwise>
              <input type="radio" name="{../@name}" value="{@value}"/>
&#160;<xsl:value-of select="."/><br/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
      </div>
    </xsl:otherwise>
  </xsl:choose>
  <xsl:if test="@mandatory = 'yes'">
    <div style="display:inline"><font class="error">&#160;*</font>
</div>
  </xsl:if>
</xsl:template>

Figure 6-12 and Figure 6-13 show how the result page differs when the number of select options climbs above five.

Figure 6-12 Smaller versus larger option sets (smaller set).

That's it for multichoice input controls. The formValidate( ) function will need to be updated to mimic your presentation rules when checking for mandatory fields.


Putting it All Together

XML can affect all aspects of an application's UI design. This chapter touched on a few of those areas: layout, navigation, and user input controls. Your UI, however, need not stop there in its use of XML. From modeling generic user actions to creating drag-and-drop targets, XML can have an impact on your application and development timelines in a variety of ways. Thoughtful use of the technology can cut the time to delivery of a project in half as well as make your application much more maintainable over the long haul.

XML encourages you to think about your UI in a more abstract way, which leads to a more modular and generic design that can be adapted to new situations and requirements. In this chapter we used XML to model various aspects of our UI. In the case of the WYSIWYG text area, we saw how to extend the implementation of that model to provide additional functionality. In Chapter 7 we will examine how to use XML for sending messages between different applications.

Figure 6-13 Smaller versus larger option sets (larger set).


XML-Based Messaging

One recent but rapidly growing application of XML is in communications, especially as applied to messaging. This chapter explores how XML has emerged as the ideal messaging solution for solving some of the most difficult issues of applying the distributed computing paradigm in Web-based environments.

We begin with a discussion about the history and technology of messaging as a distributed computing paradigm and why XML is seen as an evolutionary technology in the field of messaging. We will then take a detailed look at two of the XML-based messaging technologies, SOAP and Web Services, and show you how you can build and deploy Web Services using a variety of tools and languages. Finally we will take a first stab at understanding the Microsoft BizTalk Initiative and find out how XML is being used there to solve application integration problems.


Messaging Overview

Before we can understand "messaging," we must define a "message." Although no generally accepted definition exists, in the context of our XML discussion we can define a message as a self-contained unit of information. A message usually has a header, which contains control information about the message. Examples of control information are routing, authentication, and transactional data. A message also has a body, which contains the actual content of the message.

Messaging, therefore, is the process of passing messages between applications over a network using a transport protocol to perform a particular function.

The Beginnings of Messaging

Traditionally the term "messaging system" was often used to refer to a type of middleware system known as Message-Oriented Middleware (MOM). Common examples of MOM systems include Microsoft Message Queue Server (MMQS), IBM MQSeries, and Tibco Rendezvous. Among other features, MOM systems allow applications to exchange messages asynchronously so that the caller (the client) doesn't need to wait while the called application (the server) completes the request. Today messaging does not necessarily mean that a MOM system is being used to exchange and manage the messages. It doesn't even have to mean that you're sending messages asynchronously. Instead, messaging can be either synchronous or asynchronous, and MOM systems can be regarded as a special type of transport protocol used to enable messaging. Other common transport protocols used for messaging include HTTP and SMTP.

The Need for Messaging

To better understand the need for messaging, we will compare it to another distributed computing paradigm that has enjoyed widespread adoption: the Remote Procedure Call (RPC) paradigm. RPC today forms the basis for almost all distributed computing platforms, such as DCOM, CORBA, and Java RMI. RPC provides applications with the ability to make function calls across the network. (See the "The RPC Mechanism" sidebar.) By comparing these two paradigms you will get a better idea of the advantages and disadvantages of messaging as a distributed computing paradigm.

Messaging systems usually have little infrastructure because, in contrast to RPC, messaging generally does not support marshaling,unmarshaling and function invocation. Messages are transferred as they are, and messaging software makes no assumption about the structure of these messages. Applications using messaging must parse their own messages and invoke the appropriate functions to handle them. The only contract in a message-oriented system is that the client and server should agree about the format of the messages exchanged. In fact, several messages can be used together to invoke a given function. Messaging systems are, therefore, more loosely coupled than RPC systems because the client is more independent of the server. In addition, messaging systems can often support complex and flexible message formats such as hierarchical and binary data. RPC systems, however, generally support only common programming language data types such as integer, double, string, and sometimes array.

Because the RPC paradigm requires an infrastructure to handle the marshaling,unmarshaling, and function-invocation services, it is usually tied to a specific transport protocol. Messaging, on the other hand, can generally be transport-protocol independent. Still, using RPC is usually easier because it handles all the data packaging and method invocations for you. Messaging is useful when you need every last ounce of performance or your messages cannot be easily represented as method parameters. Table 7-1 summarizes the differences between message-oriented and RPC-oriented systems.

Table 7-1 Message-Oriented vs. RPC-Oriented Systems
Feature | Messaging | RPC
Coupling between client and server | Loose | Tight
Possible data structures | Complex, including hierarchical | Limited to common programming language data types such as integer and string
Implementation | Relatively difficult | Easy
Programming language | Independent | Tied to a set of programming language
Transport protocol | Can be bound to different transport protocols | Usually bound to a particular protocol

Messaging systems shine in situations where it would be difficult for both the caller and called parties to agree on a common function interface and operating platform. For example, two companies communicating over the Internet might have different business systems and might not want to use the same programming languages. In addition, they might prefer to communicate asynchronously so that neither will depend on the other's application being up and running.

Features of MOM Systems

Most MOM systems support guaranteed delivery of messages using a store-and-forward mechanism. Delivery is usually achieved by writing the incoming messages out to persistent queued storage. This mechanism will then deliver all the messages to the recipient when it becomes available. MOM systems usually provide two types of messaging models as well. One is point-to-point queuing and the other is referred to as publish-and-subscribe.

In the point-to-point messaging model messages are sent, through a given queue, from a sender to a single intended recipient or a group of recipients. When the recipient is a group, only one recipient in the group can consume each message. The MOM system ensures that each message is consumed only once by the next available recipient in the group.

In a publish-and-subscribe message model a sender can broadcast a message through a virtual channel to one group of recipients or more. MOM systems usually provide other enterprise-level features such as redundancy, load balancing, and transaction support.

Most mature organizations have legacy and new applications that have been implemented independently and cannot easily interoperate. Integrating these systems is called Enterprise Application Integration (EAI), and a MOM system is usually central to achieving integration. MOM systems can be used to help exchange and coordinate the data and events communicated between these systems in the form of messages, while letting the systems remain independent, both physically and architecturally.

MOM systems are also considered central to Business-to-Business (B2B) solutions because they allow organizations to cooperate without requiring them to have the same systems or to tightly integrate their systems through proprietary software.

The RPC Mechanism

RPC uses a request-and-reply communication model to allow a client application to invoke functions exposed by a remote server application that is listening for these requests. As far as the programmer is concerned, the remote invocation is similar to invoking an ordinary local function. The RPC Run-time System (RTS) providing the RPC mechanism handles all the underlying communication to make details look simple to the application programmer.

An RPC mechanism has two aspects: definition and execution. The definition, called the interface definition, is done before the RPC is actually made. It includes describing the function, its arguments, and the return value, including the associated data types. The format, or grammar, of this definition is called the Interface Definition Language (IDL). IDL is used to generate a piece of software called a stub or proxy that is linked to both the RPC client and server.

The execution starts when the client makes an RPC request. The stub linked to the client intercepts the request and redirects it to the client-side RTS. This RTS packages the arguments in a form suitable for transmission over a network. This process is known as marshaling. The client-side RTS then locates and invokes the server-side RTS that, in turn, invokes the appropriate server stub. The server stub performs the operations complementary to those of the client stub: unmarshaling the parameters passed to the function, calling the function, and marshaling the return parameters. This whole process is depicted in Figure 7-1.

Figure 7-1 Interaction of the RPC components.


XML-Based Messaging

By the 1990s the various distributed computing paradigms were feature-rich and slowly gaining widespread support. Distributed computing technologies such as CORBA and the various enterprise-class MOM systems became well supported by many vendors and several free software projects and were available for different programming languages and operating systems.

The world of distributed computing had, up to that point, consisted of several large factions such as Microsoft DCOM and CORBA, each adhering to its own particular protocols and each with its own preference for operating platforms. Interoperability between different distributed object platforms was difficult to achieve in a cost-effective manner. Most distributed object platforms were also complex and had steep learning curves. Most of them also required substantial development and administrative efforts.

Enterprises originally did not find these issues with the available distributed object computing platforms crippling. They decided to standardize on one particular platform so interoperability wasn't a problem. Technologies like MOM systems and software bridges also helped handle the cases where it was necessary to cross the divides.

The most significant issue with the old distributed computing systems was their sheer complexity. In an attempt to capture the largest market share, all the major players in the distributed computing systems industry apparently fixated on aggressively producing the most technologically advanced solutions, overlooking some of the priorities and compelling needs of the clients. While maybe 10 to 20 percent of the potential users could truly benefit from the broad range of services provided by these systems, most clients would happily trade that wealth of features for an easier technology that could simplify development and deployment.

The second problem was interoperability. With the dawn of the Internet, enterprises found themselves faced with demands for conducting business transactions over the Web. To realize the full vision of E-commerce, seamless B2B integration, and so on, it became clear that the old walls of distributed computing needed to come down.

The goal of XML-based messaging is to eliminate these complexity and interoperability issues with traditional distributed computing platforms. It attempts to do so by following the same model attributes as the other successful Internet standards: simplicity, flexibility, platform neutrality, and open text-based encoding.


The SOAP Messaging Framework

SOAP is an XML-based lightweight protocol designed to work with existing Internet and XML open standards. SOAP was originally designed to support the RPC mechanism over HTTP. Later versions removed these restrictions, and SOAP can now be used for both RPC-oriented and message-oriented operations. SOAP is also transport protocol independent.

The first official SOAP specification (SOAP 1) was announced by Microsoft and DevelopMentor in December 1999. IBM and a number of other key industrial players later joined the SOAP specification working group, and in April 2000 the SOAP 1.1 specification was released. In May 2000 the SOAP 1.1 specification was submitted to the W3C as a Note by Microsoft, IBM, UserLand, and DevelopMentor. SOAP is now under the care of the W3C's XML Protocol Working Group ( http://www.w3.org/2000/xp/ ). In this chapter we will look at the more widely supported version 1.1 specification, which can be found at http://www.w3.org/TR/SOAP/. You can find more detailed coverage of the SOAP specifications in Chapter 13.

SOAP rapidly gained widespread industrial acceptance, and today is a core enabler technology for the so-called "Software as Service" revolution in the computer industry. We will have more to say about this revolution, which created what are known as Web Services, later in this chapter.

The SOAP messaging framework consists of three parts: a message structure specification, an encoding standard, and an RPC mechanism. We'll go over each of these parts briefly next.

The Message Structure

A SOAP message is an XML document composed of an outer envelope that can contain a header and a body. This structure is depicted in Figure 7-2.

Figure 7-2 The structure of a SOAP Message.

Listing 7-1 is a skeletal SOAP message with no information in it.

Listing 7-1 soap_skeletal.xml: A skeletal SOAP message.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
  <SOAP-ENV:Header>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The <SOAP-ENV:Envelope> element is the root element of a SOAP message. It is also used to specify options that are global to the whole message, such as the encoding rules of the parameters. <SOAP-ENV:Header> is an optional element that provides a mechanism for participants to add extended information about a call. It's typically used to provide contextual information such as that related to security, transaction, payment, and so forth. The <SOAP-ENV:Body> element is mandatory and is where the actual contents of a message reside. This is where method parameters are encoded and packaged.

A special type of SOAP message, called a fault, is used to indicate when a request has failed for some particular reason, such as an unrecognizable header field, an authentication error, or some errors that occur while processing the message.

Encoding Rules

SOAP has well-defined (some might even say overly designed) rules for encoding complex data structures such as arrays and structures. For example, the following is an array of string encoding in SOAP:

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[4]">
  <SOAP-ENC:string>Item 1</SOAP-ENC:string>
  <SOAP-ENC:string>Item 2</SOAP-ENC:string>
  <SOAP-ENC:string>Item 3</SOAP-ENC:string>
</SOAP-ENC:Array>

In addition, SOAP adopts all the types defined in the XML Schema Datatypes specification (http://www.w3c.org/TR/xmlschema-2/). Therefore, for simple types, the XML Schema Datatypes can be used directly in the element schemas.

The SOAP RPC Mechanism

SOAP encoding rules also provide the marshaling and unmarshaling mechanism required to provide RPC-oriented operations. SOAP specifies an RPC mechanism that allows applications to invoke functions on the receiving end of a SOAP message. Function arguments are marshaled using the standard encoding rules as part of the SOAP message body. For instance, the following C# method:

public int GetValue( int Arg1, int Arg2 );

can be marshaled as the following SOAP message:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3c.org/2001/XMLSchema/"/>
  <SOAP-ENV:Body>
<GetValue>
  <Arg1 xsd:type="xsd:int"></Arg1>
  <Arg2 xsd:type="xsd:int"></Arg2>
</GetValue>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Note that the method name, GetValue, is encoded as the immediate child element of the <SOAP-ENV:Body> element.


Web Services

The computer technology industry is well known for its mastery of the hype life cycle, but rarely has a concept emerged from obscurity to the "new thing" as quickly as Web Services. Vendors large and small are betting that this emerging technology trend will take hold and enable new possibilities for E-business. But what exactly are Web Services?

The first important fact you should know is that Web Services is not a single technology but a group of closely related, emerging technologies that form a distributed computing architecture based on open and ubiquitous Internet standards.

Although on the surface this might sound like the Application Service Provider model, Web Services are very different. Application Service Providers are entire Web-based applications, while Web Services are reusable software components that provide discrete functionality. Also, while Application Service Provider applications are implemented as a closed "black box," Web Services are designed to be inherently extensible.

For an example, look at the current crop of Web-based auction sites such as eBay or uBid. All these sites offer HTML-based auction services that are meant to be used by people. However, imagine that a company has found a need for auction functionality in its own applications. This company will either need to implement the auction functionality itself or redirect customers to one of the auction sites. If these auction sites have a Web Services interface, this company can remotely invoke the needed functionality to build its own applications.

As we saw earlier, Web Services are not implemented in a monolithic way, but represent a collection of several related technologies. From a developer's point of view, Web Services can be understood as specific implementations of an XML-based messaging system built on the following infrastructure components:

  • Web Service Description To be able to interact with a Web Service, a client must be made aware of the semantics of its services. The WSDL is an XML document used to describe the interface to a Web Service in a structured and standardized way.
  • Web Service Wire Formats SOAP is used as the standard wire format for Web Services. Web Services can also use plain HTTP, GET, and HTTP POST, but most are expected to use SOAP and to use HTTP POST as the transport protocol.
  • Web Service Discovery Web Services make their presence, capabilities, and functionality known throughout the process of discovery. The UDDI specification defines an XML schema and an API that enable clients to dynamically find Web Services.

These technologies together, HTTP + XML + SOAP + WSDL + UDDI, form the core low-level technology stack of Web Services.

Other higher-level technologies are being developed for providing strategic aspects of business processes. One of these technologies is the Web Services Flow Language (WSFL) sponsored by IBM. WSFL aims to define a framework whereby Web Services implementers can describe the business logic required to assemble various services into an end-to-end business process.

Describing Web Services with WSDL

WSDL is the IDL of Web Services. WSDL describes the functionality of a Web Service and how this functionality is made available, and plays a central role in enabling Web Service interoperability. It does so by fully describing the capabilities and use of the service in a platform- and protocol-independent manner. The description is so complete that tools can use WSDL to automate the creation of code such as proxies.

WSDL is a W3C-submitted specification and is supported by a number of industry leaders, including Microsoft and IBM. Version 1.1 of the public specification can be found at http://www.w3.org/TR/wsdl .

To illustrate the structure of a WSDL document, let's imagine we have a Web Service, called HelloService, which exposes a single method with the following method signature (shown here in C#):

String SayHello( String Name )

The idea is that the SayHello( ) method, when invoked, will return a string built by concatenating the constant string Hello with the Name parameter passed in the call. So, for example, SayHello( Joe ) will return the string Hello Joe. Listing 7-2, sayhello_request.xml, is a sample SOAP request.

Listing 7-2 sayhello_request.xml: The SOAP message for the SayHello method.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://
schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <SayHello xmlns="http://mspress.microsoft.com/corexml/">
      <Name>Joe</Name>
    </SayHello>
  </soap:Body>
</soap:Envelope>

As you can see, this is just a standard SOAP message using the RPC encoding rules we saw previously when discussing SOAP. Listing 7-3 is a possible SOAP response for our request.

Listing 7-3 sayhello_response.xml: A response message for sayhello_request.xml.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://
schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <SayHelloResponse xmlns="http://mspress.microsoft.com/corexml/">
      <SayHelloResult>Hello Joe</SayHelloResult>
    </SayHelloResponse>
  </soap:Body>
</soap:Envelope>

This response message returns the concatenated string Hello Joe to the SOAP client.

Now we need to talk about describing the service that handled this request and returned our response. To do so we must look at the specifics of the syntax as well as its overall semantics, or structure, in terms of XML. Every WSDL document starts off with the <definitions> root element, and under this are the following five major sections/elements:

  • <types> Defines the data types that the Web Service operates on. The <import> element can be used in place of the <types> element to reference an external schema document.
  • <message> Describes the format of the individual request and response messages. The types of the parameters can either be referenced from those defined in the <types> section or defined directly using the XML Schema default data types.
  • <portType> Defines the request, response messages, or both (defined in the <message> section) that make up the individual methods (called Web methods) in a Web Service.
  • <binding> Specifies the encoding and protocol used by the Web methods defined in the <portType> section.
  • <service> Used to identify the name and physical location of a Web Service—in other words, the endpoint that services call, as well as any bindings the Web Service uses.

All of this probably sounds foreign to you, but hold tight. After we show you a quick example of describing our sample Web Service in WSDL, we're going to spend a few pages discussing each of these items.

To describe our service we will need a file called helloservice.wsdl (in Listing 7-4). This will be the WSDL document for our imaginary HelloService Web Service.

Now that we have an example under our belt, let's look at each of the WSDL elements in more detail.

Listing 7-4 helloservice.wsdl: A sample WSDL document.

<?xml version="1.0" encoding="utf-8"?>
<definitions name="HelloService" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://mspress.microsoft.com/corexml/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
             targetNamespace="http://mspress.microsoft.com/corexml/">
  <types>
    <xsd:schema targetNamespace="http://mspress.microsoft.com/corexml/">
      <xsd:element name="SayHello">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="Name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="SayHelloResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="SayHelloResult" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </types>
  <message name="SayHelloSoapIn">
    <part name="parameters" element="tns:SayHello"/>
  </message>
  <message name="SayHelloSoapOut">
    <part name="parameters" element="tns:SayHelloResponse"/>
  </message>
  <portType name="HelloServiceSoapPortType">
    <operation name="SayHello">
      <input message="tns:SayHelloSoapIn"/>
      <output message="tns:SayHelloSoapOut"/>
    </operation>
  </portType>
  <binding name="HelloServiceSoap" type="tns:HelloServiceSoapPort">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>
    <operation name="SayHello">
      <soap:operation soapAction="http://mspress.microsoft.com/
corexml/SayHello"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="HelloService">
    <port name="HelloServiceSoap" binding="tns:HelloServiceSoap">
      <soap:address location="http://localhost/HelloService.asmx"/>
    </port>
  </service>
</definitions>

Types

In WSDL, types are defined using XML Schema. The schema can either be embedded within the <types> element, or the <import> element can be used to reference an external schema document. In either case the schema itself will be identical.

In helloservice.wsdl we used XML Schema to define the types we need for the request and response SOAP messages. The following is a snippet of code from that file:

  <types>
    <xsd:schema targetNamespace="http://mspress.microsoft.com/corexml/">
      <xsd:element name="SayHello">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="Name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="SayHelloResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="SayHelloResult" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </types>

This part of our document is standard XML Schema. For the request message we define that we will accept a single parameter called Name, and that it will be an XML Schema xsd:string type. For the response message we define the return value in SayHelloResult, and it is also of XML Schema xsd:string type.

The targetNameSpace, http://mspress.microsoft.com/corexml/, matches the local type namespace (tns) declared for the WSDL. This tells the schema processor that the types defined in this schema are part of that namespace, and we can refer to them later using that namespace prefix.
The <types> element is optional and is meant for defining complex types such as C# structures. For simple types, such as our Name parameter, you can omit this section all together and use XML Schema directly when you need to reference the parameter types in the <message> section. We will show you how when we discuss the <message> section later.

Instead of embedding a schema definition directly in a WSDL document, you can alternatively use the <import> element to reference an external schema. The following shows an example use of the <import> element:

<definitions name="HelloService" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:tns="http://mspress.microsoft.com/corexml/"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             targetNamespace="http://mspress.microsoft.com/corexml/">
  <import location=http://mspress.microsoft.com/corexml/
HelloService.xsd/>
</definitions>

In this example you can see we used the location attribute to specify the actual URL that can be used to import the schema.

Message

A <message> element corresponds to a single piece of information exchanged between the client and the service. A typical request-response method call has two corresponding messages, one for the request and one for the response.

The name attribute is used to uniquely identify a specific message so it can be referenced in the <portType> section later. The subelement <part> is used to define the name and type for a single method parameter or return value. For example, in helloservice.wsdl the following message defines that a message called SayHelloSoapIn has a single parameter of a complex type, called SayHello, which is defined in the <types> section:

  <message name="SayHelloSoapIn">
    <part name="parameters" element="tns:SayHello"/>
  </message>

The element attribute is used to reference types defined in the <types> section. However, if the type of a parameter can be expressed as an XML Schema datatype, you can use the type attribute instead. The following is an example:

  <message name="SayHelloSoapIn">
    <part name="parameters" type="xsd:string"/>
  </message>

Here we have declared that our parameter is a simple string type. If all your parameters can be defined using the Schema default data types, you can omit the <types> section altogether.

A WSDL document needs to declare <message> sections for all the possible ways that the Web methods can be invoked, and all the possible responses. For example, if your Web Services support protocols other than SOAP, such as HTTP GET and HTTP PUT, you will need to include those <message> sections as well.

PortType

The <portType> section identifies the set of operations and the messages involved with each of the operations that are exposed by a Web Service. A <portType> completes the interface definition part of a Web Service.

The <operation> section within a <portType> represents the definition of a specific Web method. The name attribute specifies the name of this Web method. Within the <operation> section we then find the <input> and <output> elements that reference the message types defined in the <message> sections to define the request and response messages, respectively. Depending on the presence and ordering of the <input> and <output> elements, four types of operations are possible.

  • <input> A one-way, or write-only, method that accepts a request from a client but does not respond.
  • <input><output> A request-response method that accepts a request from a client and sends this client a reply.
  • <output><input> A solicit-response method that sends a message to the client and expects a reply back from the client.
  • <output> A notification, or read-only, method that sends a message to a client.

For example, in helloservice.wsdl the SayHello Web method is defined as a request-response method.

  <portType name="HelloServiceSoap">
    <operation name="SayHello">
      <input message="tns:SayHelloSoapIn"/>
      <output message="tns:SayHelloSoapOut"/>
    </operation>
  </portType>

Binding

The <binding> section is used to specify the protocol details for the various operations defined in the <portType> section and to specify the on-the-wire representation of the messages. The content of the <binding> section is mostly specific to the protocol to which the <portType> is being bound, but every <binding> section will have the following structure:

<binding name="..." type="...">
  <!-- binding details for the portType to be bound -->
  <operation name="...">
    <!-- binding details for this operation -->
    <input>
      <!-- binding details for this message -->
    </input>
    <output>
      <!-- binding details for this message -->
    </output>
  </operation>
</binding>

Each <binding> element has a name attribute that uniquely identifies the binding. It also has a type attribute that specifies the <portType> element to which this particular binding applies.

First the binding details for the <portType> can be specified. Then you specify the binding parameters for each of the operations defined in the <portType> section specified in the type attribute. Finally you specify the binding details for each of the input messages, output messages, or both defined (in the same order) in the original <portType>. Most Web Services will use SOAP as the wire protocol and, therefore, the SOAP request-response operation is the typical binding type. For example, here is the <binding> section for HelloService, which is a SOAP-based Web Service:

<binding name="HelloServiceSoap" type="tns:HelloServiceSoapPortType">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>
    <operation name="SayHello">
      <soap:operation soapAction="http://mspress.microsoft.com/
corexml/SayHello"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>

The SOAP binding parameters are specified using four basic elements, all of which belong to the http://schemas.xmlsoap.org/wsdl/soap/namespace. The <soap:binding> element is used to specify details about the transport used. The transport attribute specifies the transport protocol (HTTP, SMTP, FTP, and so forth) used and is specified using a standard URI. The style attribute specifies whether the operation specified by this <portType> is RPC- or document-oriented. This element must be placed before any <operation> elements.

For SOAP-over-HTTP binding the <soap:operation> element is used to specify the SOAPAction header of the HTTP request. The <soap:body> and the <soap:header> elements can be used within both the <input> and <output> elements and to specify how the messages they represent appear inside the <Header> and <Body> elements of the SOAP <Envelope> element.

Further details of these and other binding elements for other protocols are beyond the scope of this book, but you can refer to the WSDL specifications at http://msdn.microsoft.com/xml/general/wsdl.asp if you're interested.

Service

The final section, <service>, is used for the collection of bindings that make up a service and also to attach these to physical addresses. This element has a single attribute, name, that is used to uniquely identify the Web Service. One or more <port> elements within it can be used to specify the different bindings and the associated operations that this Web Service supports. The binding attribute specifies the name of the <binding> section to use. Each <port> element will contain elements from the binding-specific namespace, for example, <soap:address>, that are used to specify the addressing details in the form needed by that particular binding.

In helloservice.wsdl our <service> section specifies that we support only one binding, namely HelloServiceSoap, and that the associated operations can be accessed at http://localhost/HelloService.asmx.

<service name="HelloService">
    <port name="HelloServiceSoap" binding="tns:HelloServiceSoap">
      <soap:address location="http://localhost/HelloService.asmx"/>
    </port>
  </service>
Although nothing will prevent you from manually writing the WSDL for your Web Services, you should realize that certain tools, such as ASP.NET and the IBM Web Services Toolkit, can help you generate WSDL automatically.

Creating a Web Service

In this section we will show you how you can implement a Web Service and how to invoke methods exposed by the service. We first use the tools provide by the .NET Framework, such as ASP.NET, to implement the service. In later sections, to illustrate the language-independent nature of Web Services, we will show you how you can build consumers, that are not .NET, that can invoke the services provided by the .NET provider.

At the time of this writing the Microsoft .NET Framework SDK is still beta software. The examples here were tested on beta 2 of .NET SDK. You can download the .NET SDK and find information on .NET at the Microsoft MSDN site ( http://msdn.microsoft.com/net/ ).

Although we have more complex examples in this book, we will use ASP.NET to build a simple Web Services provider and consumer. As you will see, ASP.NET makes creating Web Services almost effortless, which should interest all developers.

Our example provides stock quote information over the Web. Of course, we will not be providing actual real-time stock prices and information in this example, that is clearly beyond the scope of this book, but we will provide some hard-coded stock information.

Creating the Provider

Creating a Web Service provider can be summarized as a four-step process. In the first step we will need to create a new source file for the provider. This file will serve as the entry point to our service. The source file will be a plain text file with an .asmx extension (as opposed to an .aspx extension for an ASP.NET Web application). You need to place this file in an Internet Information Server (IIS) virtual directory that has the executescripts permission turned on.

The first line of this file should contain the special ASP.NET directive:

<%@ webservice %>

This directive allows you to declare the programming language used to develop this provider and the name of the implementation class. You have the option to include this implementation class in the same file, but that is not a requirement.

Next we will need to instruct our implementation to inherit from the WebService class found in the System.Web.Services namespace. By inheriting from WebService, your provider can gain access to common ASP.NET objects such as Application, Session, User, and Context. If you want your provider to be publicly consumed via the Web, you must specify an XML namespace for your Web Service. This task is accomplished by tagging the implementation class with the appropriate namespace attribute. ASP.NET uses the value of this attribute in the WSDL document and the SOAPAction HTTP header field to uniquely identify the Web-callable endpoints. If you don't specify a namespace attribute, ASP.NET uses a default value of http://tempuri.org/. Finally any methods you want to be accessible via the Web should be tagged with the WebMethod attribute.

The stockservice.asmx file in Listing 7-5 contains the code for our example stock quotes Web Service provider. StockService is designed to return the stock quote information given a stock code. For now, don't worry about how this service can be invoked. We will get to that in a later section when we show you how to implement a consumer for this service.

Listing 7-5 stockservice.asmx: A sample ASP.NET Web Service that returns stock information.

<%@ webservice language="C#" class="StockService" %>
using System;
using System.Collections;
using System.Web;
using System.Web.Services;
[WebService(Namespace="http://mspress.microsoft.com/corexml/")]
public class StockService : WebService
{
  /* Hashtable used to store our sample stocks */
  private static Hashtable Stocks;
  public StockService()
  {
    StockInfo si;
    /* the following code populates our Stocks Hashtable with some sample stocks */
    Stocks = new Hashtable();
    si = new StockInfo();
    si.Code = "MSFT";
    si.Company = "Microsoft Corporation";
    si.Sector = "Technology";
    si.Industry = "Software and Programming";
    si.Price = 73.00;
    Stocks.Add( si.Code, si );
    si = new StockInfo();
    si.Code = "IBM";
    si.Company = "Int'l Business Machines";
    si.Sector = "Technology";
    si.Industry = "Computer Hardware";
    si.Price = 113.00;
    Stocks.Add( si.Code, si );
    si = new StockInfo();
    si.Code = "GE";
    si.Company = "General Electric Company";
    si.Sector = "Conglomerates";
    si.Industry = "Conglomerates";
    si.Price = 48.00;
    Stocks.Add( si.Code, si );
    si = new StockInfo();
    si.Code = "SONY";
    si.Company = "Sony Corporation";
    si.Sector = "Consumer Cyclical";
    si.Industry = "Audio & Video Equipment";
    si.Price = 65.80;
    Stocks.Add( si.Code, si );
  }
  /* returns StockInfo object for a given stock code */
  [WebMethod(Description="Returns company info and current quote
price for a given stock code",EnableSession=false)]
  public StockInfo GetStockInfo( string Code )
  {
    return ( InternalGetStockInfo( Code ) );
  }
  /* returns the quote price for a given stock code */
  [WebMethod(Description="Returns current quote price for a given
stock code",EnableSession=false)]
  public double GetQuotePrice( string Code )
  {
    StockInfo si = InternalGetStockInfo( Code );
    if ( si.Code.Equals( Code ) )
    {
      return ( si.Price );
    }
    else
    {
      return ( 0.00 );
    }
  }
  /* Internal method that returns a StockInfo object for a
given stock code. Note that in a real application, you will
probably be calling another service or querying a database
here to obtain the information. */
  private StockInfo InternalGetStockInfo( string Code )
  {
    StockInfo si;
    if ( Stocks.ContainsKey( Code ) )
    {
      si = (StockInfo)Stocks[ Code ];
    }
    else
    {
      si = new StockInfo();
      si.Code = "";
      si.Company = "";
      si.Sector = "";
      si.Industry = "";
      si.Price = 0.00;
    }
    return ( si );
  }
}

/* struct used to hold stock information */
public struct StockInfo
{
  public string Code;
  public string Company;
  public string Sector;
  public string Industry;
  public double Price;
}

The first line contains the Web Service ASP.NET directive, where we declare that our provider is written in C# and the implementation class is called StockService.

<%@ webservice language="C#" class="StockService" %>

Next we import the necessary libraries. In our case these are the System, System.Collections, System.Web, and System.Web.Services namespaces. The System.Web and System.Web.Services namespaces reference the ASP.NET and Web Services objects and types. The System.Collections namespace contains the Hashtable class that we're going to use to store the stock information.

We declare our implementation class, StockService, to inherit from the WebService class. We have also tagged our Web Service with an XML namespace called http://mspress.microsoft.com/corexml/. For your Web Services you should substitute this with your personal or your company's XML namespace.

[WebService(Namespace="http://mspress.microsoft.com/corexml/")]
public class StockService : WebService

For this example we used a Hashtable to store our stock information. The StockService constructor initializes this Hashtable with a few sample stocks. Note that we've used a C# struct for storing the various pieces of data that constitute our stock information. This struct, called StockInfo, is declared at the bottom of the StockService.asmx file.

Methods of an implementation class do not automatically have the ability to be invoked over the Web. These are called Web methods. To define a Web method, apply the WebMethod attribute to any public methods that you want exposed as Web methods. Our Web Service has two Web methods, GetStockInfo( ) and GetQuotePrice( ). GetStockInfo( ) returns a StockInfo struct for a given stock code and GetQuotePrice( ) returns the quote price for a given stock code.

Like many other attributes, the WebMethod attribute supports several optional properties, as shown in Table 7-2.

Table 7-2 WebMethod Attributes
Property | Parameter | Description
BufferResponse | Boolean | Specifies whether this Web method's output made through the ASP.NET Response object is buffered.
CacheDuration | int | Specifies the time, in seconds, to keep this method's response. Default is not to cache (zero seconds).
Description | string | Provides a description for this Web method. This will be included in the WSDL.
EnableSession | Boolean | Specifies whether you want to use session state for this Web method. The default is true.
MessageName | string | Lets you specify a name for your Web method that is different from that specified in the implementation class. This is usually used for overloaded methods.
Transaction | enumerated (Disabled, NotSupported, Supported, Required, RequiresNew) | Specifies the Transaction mode for this Web method. Default is Disabled.

That's all you need for a simple Web Service! You don't even have to compile the source file. Simply place the StockService.asmx file in an IIS virtual directory and you can access your Web Service from any Web browser. For our example we use the default/root virtual directory, so our Web Service is accessible through the following URL:

http://localhost/StockService.asmx

Now that we've built our Web Service provider, let's look at how we can consume it.

Consuming Web Services

One of the many useful features provided automatically for you when you develop Web Services using ASP.NET is a built-in consumer you can use interactively from any browser. Before we show you how you can build your own consumer, it is instructive to take a closer look at this default consumer first.

The Built-In ASP.NET Consumer

To see this built-in consumer in action for yourself, fire up your favorite browser and point it to our StockService.asmx URL. Figure 7-3 shows what you will see when you open this link in Microsoft Internet Explorer.

Figure 7-3 Calling StockService.asmx in Internet Explorer.
This Web page is generated automatically through the use of reflection by ASP.NET with no coding required on your part.

The first page, shown in Figure 7-3, lists all the Web methods you've defined in your Web Service as clickable links. These links will take you to the autogenerated pages that you can use to invoke these methods. In addition, this page also provides a link that generates the WSDL document for your Web Service. Figure 7-4 shows what you will see when you click on the Service Description link.

Figure 7-4 The autogenerated WSDL of StockService.
Like so many features you've seen so far, this WSDL was generated automatically by ASP.NET. You simply write your Web Service and ASP.NET will use reflection to automatically generate the corresponding WSDL document for you.

If you click on one of the Web methods listed in Figure 7-4 you will see something like Figure 7-5 (which shows the page for the GetStockInfo( ) method).

Figure 7-5 Invoking a Web method from Internet Explorer.

You should realize that this page is a true consumer in every right. This consumer, which can be a great tool for testing, uses HTTP GET to communicate with your Web Service provider. It provides an HTML form to let you enter values for the parameters of the Web methods. In the case of our GetStockInfo( ) Web method you can enter a value for the Code parameter. Figure 7-6 shows the resulting Web page when we enter "MSFT" in the Code input box.

Figure 7-6 The result page generated by invoking the GetStockInfo( ) Web method in Internet Explorer.
This is the response when you invoke your Web method using HTTP GET or HTTP POST, which is what this particular consumer uses. A SOAP response will be slightly different.

Going back to the GetStockInfo Web method page shown in Figure 7-5, you see that, in addition to letting you invoke a Web method, you also get useful descriptions of how to access this particular method using SOAP, HTTP GET, and HTTP POST. You also see what their respective responses will look like. For example, this page shows that to invoke the GetStockInfo( ) method via SOAP, you must make the following HTTP transaction:

POST /StockService.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://mspress.microsoft.com/corexml/GetStockInfo"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap=

"http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetStockInfo xmlns="http://mspress.microsoft.com/corexml/">
      <Code>string</Code>
   </GetStockInfo>
  </soap:Body>
</soap:Envelope>

As you can see, the built-in ASP.NET consumer is feature rich. However, it is intended for interactive use, not for programmatic access. To be able to access the StockService Web Service programmatically, we need to create our own consumer.

Writing our own consumer might look like a daunting task on the surface, especially when you consider that a truly useful consumer will need to deal with factors like SOAP and WSDL directly. Luckily, ASP.NET comes to our rescue once again. Now let's learn how we can use the features and tools provided by the .NET Framework.

Creating a .NET Consumer

Implementing a consumer using the .NET Framework is surprisingly simple. The task is greatly simplified through the use of proxy classes, which contain built-in functionality to automatically handle all the gory details of invoking XML Web services, such as making HTTP requests and constructing and parsing SOAP messages. The goal is to invoke services as easily as you would invoke local objects. The great feature in .NET is that you don't have to create these proxy classes for your services. .NET provides several tools to help you generate proxy classes to XML Web services automatically; one of them is wsdl.exe.

The following command line shows how we can use wsdl.exe to generate a proxy class for our StockService Web Service. The wsdl.exe application uses the information found in this WSDL document to build a proxy class tailored to your Web Service specifications.

wsdl /language:cs /protocol:soap http://localhost/StockService.asmx?wsdl

This command line will generate a proxy class file called StockService.cs. The first parameter, /language:cs, indicates that we want to generate a proxy class written in C#. The second parameter, /protocol:soap, specifies that the generated proxy class will use the SOAP protocol to talk to the StockService WebService. Other valid values are httpget and httppost for the HTTP protocol GET and POST methods. The last parameter specifies the location of the WSDL document for StockService. This can be a URL or a local file specification.

Even though our StockService.cs proxy class is written in C#, by compiling this into a DLL, you can use this proxy from consumers developed in .NET-supported languages. The following command line uses the C# csc.exe compiler to compile the StockService.cs proxy class into a DLL:

csc /t:library /r:system.web.services.dll StockService.cs

Running this command line will generate a StockService DLL file that you can reference in your consumer projects. Armed with the StockService proxy class, writing a consumer for our StockService Web Service is almost trivial. Listing 7-6, net_consumer.cs, shows an example of such as a consumer.

Listing 7-6 net_consumer.cs: A .NET consumer of the StockService Web Service.

using System;
public class TestStockService
{
  public static void Main( string[] Args )
  {
    /* make sure the user has specified a stock code */
    if ( Args.Length < 1 )
    {
      Console.WriteLine( "usage: teststockservice stock_code" );
      return;
    }
    /* sc contains the user specified stock code */
    string sc = Args[ 0 ];
    /* Instantiate the StockService's proxy class */
    StockService ss = new StockService ();
    /* Invoke GetBooks() over SOAP and get the data set. */
    StockInfo si = ss.GetStockInfo( sc );
    /* Display the results */
    Console.Write( "Stock Code: " ); Console.WriteLine( si.Code );
    Console.Write( "Company   : " ); Console.WriteLine( si.Company );
    Console.Write( "Industry  : " ); Console.WriteLine( si.Industry );
    Console.Write( "Sector    : " ); Console.WriteLine( si.Sector );
    Console.Write( "Price     : " ); Console.WriteLine( si.Price );
  }
}

NET_Consumer.cs is a simple console application that, given a stock code specified on the command line, will display the respective stock information. See how we access the StockService Web Service by simply using the new operator to instantiate an instance of the StockService class?

StockService ss = new StockService ();

The ss variable now holds a reference to StockService, which can now be used to access all the Web methods exposed by the StockService Web Service. For example, in NET_Consumer.cs, to invoke the GetStockInfo( ) Web method we simply invoke the corresponding method in the proxy.

StockInfo si = ss.GetStockInfo( sc );

The rest of NET_Consumer.cs then displays the information returned in the StockInfo struct. To compile NET_Consumer.cs, enter the following command line in a console window:

csc /r:System.dll /r:StockService.dll NET_Consumer.cs

We've included a reference to the StockService DLL file we generated earlier. This instructs the compiler to link in the proxy class with NET_Consumer.cs. Figure 7-7 shows a sample output of compiling and running NET_Consumer.cs so you can see what to expect.

You've now seen how easy it is to create a consumer using ASP.NET. But what if you cannot use .NET because your consumer needs to run in an environment that is not .NET?

Figure 7-7 An example output of NET_Consumer.exe.

Creating a Non-.NET Consumer

It would be limiting if Web Services developed using the .NET Framework could be accessed only by a .NET-based consumer. Of course, nothing is further from the truth. You can access .NET-based Web Services from any consumer as long as they adhere to the Web Services specifications. In fact, when it comes to support for SOAP and Web Services, you'll find no shortage of available development toolkits from both commercial and non commercial vendors. To give you a leg up on finding some of these tools, Table 7-3 lists some of the currently available toolkits plus their descriptions and Web sites.

In our example we will use the SOAP Toolkit 2 to develop a consumer for our StockService Web Service. We will also use VBScript as the programming language for implementing this consumer. The code for this consumer can be found in Listing 7-7, soap_toolkit_consumer.vbs.

Listing 7-7 soap_toolkit_consumer.vbs: A sample consumer using the SOAP Toolkit.

' instantiate MSSOAP object
set SoapClient = CreateObject( "MSSOAP.SoapClient" )
' setup client proxy from the StockService's WSDL
call SoapClient.MSSoapInit( "http://localhost/
StockService.asmx?wsdl" )
' at this point you can access all the methods exposed by
' StockService through the SoapClient proxy object
' ask user for the stock code
code = InputBox( "Enter Stock Code" )
' invoke the GetQuotePrice method and display the result
WScript.Echo "Price = " & SoapClient.GetQuotePrice( UCase( code ) )
Table 7-3 Some SOAP/Web Services Toolkits
Toolkit | Language/Platform | Web Page URL
Microsoft SOAP Toolkit | Can be used by any COM-compatible languages/systems such as Microsoft Visual Basic, Borland Delphi, and so forth | http://msdn.microsoft.com/library/en-us/soap/htm/soap_overview_3drm.asp
IBM Web Services Too1kit (WSTK) | Java 2 | http://www.alphaworks.ibm.com/tech/webservicestoolkit
Apache SOAP | Java 2 | http://xml.apache.org/soap/index.html
SOAP:Lite | Perl | http://www.soaplite.com
Web Services for Python | Python | http://sourceforge.net/projects/pywebsvcs/

When soap_toolkit_consumer.vbs is run it will first ask you for the stock code and then display a response. Figure 7-8 shows the message box displayed in a sample response.

Figure 7-8 Running soap_toolkit_consumer.vbs.

As you can see, implementing a basic consumer using the SOAP Toolkit can be extremely easy. You just point it to a WSDL document when you initialize the proxy object through its MSSoapInit method. The proxy automatically loads and interprets the WSDL and builds a COM IDispatch interface.

If, for some remote reason, you can't find a toolkit that suits your needs, you can always roll one of your own. After all, it's just HTTP and XML. As an example, the following ASP application in Listing 7-8 uses the XMLHTTP and DOM objects provided by the MSXML version 2 parser to access the StockService Web Service.

Listing 7-8 asp_consumer.asp: An ASP consumer for the StockService Web Service.

<%
  ' retrieve submitted Stock Code value
  sCode = "" & Request.Form( "pName" )
%>
<form method="post" action="asp_consumer.asp">
  Enter Stock Code:<br>
  <input name="pName" value="<%= sCode %>"> <input type="submit" value="Submit">
</form>
<hr>
<br>
<%
  '
  if ( sCode <> "" ) then
    ' we are going to use the MS XMLHTTP object
    set oXmlHttp = Server.CreateObject( "MSXML2.XMLHTTP" )
    ' open a HTTP POST connection to StockService
    oXmlHttp.Open "POST", "http://localhost/StockService.asmx", False
    ' setup HTTP headers.
    oXmlHttp.SetRequestHeader "Content-Type", "text/xml"
    oXmlHttp.SetRequestHeader "SOAPAction",
               "http://mspress.microsoft.com/corexml/GetStockInfo"
    ' build string to hold the SOAP request message
    sSOAPMsg =
      "<soap:Envelope " & _
      " xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""" & _
      " xmlns:xsd=""http://www.w3.org/2001/XMLSchema""" & _
      " xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" & _
      "  <soap:Body>" & _
      "    <GetStockInfo " &
      "      xmlns=""http://mspress.microsoft.com/corexml/"">" & _
      "      <Code>" & sCode & "</Code>" & _
      "    </GetStockInfo>" & _
      "  </soap:Body>" & _
      "</soap:Envelope>"
    ' call StockService
    oXMLHTTP.Send sSOAPMsg
    ' retrieve response as a DOM object
    set oResponseDOM = oXmlHttp.ResponseXML
    ' extract the StockInfo node values
    set oNode =
      oResponseDOM.SelectSingleNode( "//GetStockInfoResult/Code" )
    sCode = oNode.Text
    set oNode =
      oResponseDOM.SelectSingleNode( "//GetStockInfoResult/Company" )
    sCompany = oNode.Text
    set oNode =
      oResponseDOM.SelectSingleNode( "//GetStockInfoResult/Industry" )
    sIndustry = oNode.Text
    set oNode =
      oResponseDOM.SelectSingleNode( "//GetStockInfoResult/Sector" )
    sSector = oNode.Text
    set oNode =
      oResponseDOM.SelectSingleNode( "//GetStockInfoResult/Price" )
    sPrice = oNode.Text
  end if
%>
<table border="1">
  <tr>
    <td>
      Code:
    </td>
    <td>
      <%= sCode %>
    </td>
  </tr>
  <tr>
    <td>
      Company:
    </td>
    <td>
      <%= sCompany %>
    </td>
  </tr>
  <tr>
    <td>
      Industry:
    </td>
    <td>
      <%= sIndustry %>
    </td>
  </tr>
  <tr>
    <td>
      Sector:
    </td>
    <td>
      <%= sSector %>
    </td>
  </tr>
  <tr>
    <td>
      Price:
    </td>
    <td>
      <%= sPrice %>
    </td>
  </tr>
</table>

ASP_Consumer.asp features an HTML form to let you enter the stock code you want to query and a table that displays the results of invoking the StockService Web Service. Figure 7-9 shows what ASP_Consumer.asp looks like when loaded in a browser.

Figure 7-9 Running ASP_Consumer.asp.

In this example we use the MSXML XMLHTTP object to handle the HTTP POST transactions we need to make to invoke StockService. This task is accomplished in the following line of code:

   oXmlHttp.Open "POST", "http://localhost/StockService.asmx", False

Next we set up Content-Type and SOAPAction HTTP header directives as required by the SOAP specifications:

    oXmlHttp.SetRequestHeader "Content-Type", "text/xml"

    oXmlHttp.SetRequestHeader "SOAPAction",
"http://mspress.microsoft.com/corexml/GetStockInfo"

The only task left to do now to invoke StockService is to build the SOAP <envelope> and send the HTTP POST request.

SOAPAction uses the same XML namespace we specified using the namespace attribute in the StockServer.asmx.
sSOAPMsg =
 "<soap:Envelope " & _
 " xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""" & _
 " xmlns:xsd=""http://www.w3.org/2001/XMLSchema""" & _
 " xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" & _
 "  <soap:Body>" & _
 "    <GetStockInfo " &
 "      xmlns=""http://mspress.microsoft.com/corexml/"">" & _
 "      <Code>" & sCode & "</Code>" & _
 "    </GetStockInfo>" & _
 "  </soap:Body>" & _
 "</soap:Envelope>"

 ' call StockService
 oXMLHTTP.Send sSOAPMsg

When the call is completed, we retrieve the SOAP response as a DOM object using the XMLHTTP ResponseXML property. Finally we extract the StockInfo field values using XPath and display them in an HTML table.

As you can see, invoking Web Services is not overly complicated, even without special tools. However, this approach is only appropriate for accessing simple Web Services because it can quickly become tedious and unmanageable for even slightly more complicated Web Services.

Discovering Web Services with UDDI

So far we've covered how we can describe, implement and deploy Web Services. But our work is only half done. How will clients find these Web Services on the Internet? Just like people use the yellow pages and search engines to locate Web pages, we need a way for applications and people to locate and discover the capabilities of Web Services. This is where UDDI comes in.

The Specification

The UDDI specification was jointly developed by IBM, Microsoft, and Ariba as an open standard for advertising and discovering Web Services. The UDDI specification defines a Web-based registry framework that exposes information about a business and the services it provide. The framework consists of two parts:

  • An XML Schema ( http://www.uddi.org/schema/uddi_1.xsd ) that defines data structures that can be used to perform inquiry and publishing functions against any UDDI-compliant registries.
  • A set of SOAP-based APIs that provides a programmatic interface for interacting with any UDDI-compliant registries.

A UDDI Business Registry is an implementation of the UDDI registry framework. Additionally, a UDDI Business Registry provides an HTML-based interface to allow businesses to nonprogrammatically describe themselves and the Web Services they provide and for businesses to locate other potential partners. At the time of this writing, three Web sites provide the UDDI Business Registry service:

  • The Microsoft UDDI Business Registry at http://uddi.microsoft.com
  • The IBM UDDI Business Registry at http://www.ibm.com/services/uddi/
  • The Ariba UDDI Business Registry at http://uddi.ariba.com

As you can see, these are also the same three companies that developed the UDDI specification.

You can find the specifications and other useful information and resources related to UDDI at http://www.uddi.org .

The UDDI Data Model

UDDI organizes the information stored in a UDDI registry into three broad categories. These types of information are represented by three corresponding UDDI data structures. These categories are as follows:

  • White Pages contain simple information about a business such as the name, address, contact, and other known identifiers.
  • Yellow Pages include industrial categorizations based on standard taxonomies as well as descriptive information about the services provided.
  • Green Pages contain the technical information about the services, including references and interfaces to the services.

Figure 7-10 illustrates this data model.

Figure 7-10 UDDI data structures.

These three structures, businessEntities, businessServices, bindingTemplates, represent the complete amount of information provided by the UDDI framework.

The API

The UDDI specification also defines a SOAP-based API that consists of 20 requests and 10 responses, each of which makes use of the data structures outlined previously. The API provides two levels of information. The high-level information returned by the find methods consists of general, summary-level data. The low-level information returned by the get methods contains specific, detail-level data.

For example, the information returned by the find_business UDDI method contains summary information, such as the names and addresses, of businesses that matched the search criteria provided when the method was issued. One of the pieces of information returned is the unique business registration identifier for each of the matching businesses. This identifier can be used with the low-level get_business method to get the entire business registration details.

Table 7-4 lists some of the more commonly used API methods and their corresponding response data structures.

Table 7-4 Common UDDI APIs
Method | Purpose | Response Data Structure
find_business | Locates businesses based on category, identity, registered URLs, or name. | businessList
find_service | Locates services based on category or name. | serviceList
find_binding | Locates a particular binding within a specific businessService structure. | bindingDetail
find_tModel | Locates summary information about the registered specifications and namespaces. | tModelList
get_businessDetail | Returns the entire detail of a business registration including all the associated businessServices and bindingTemplates. | bindingDetail
get_serviceDetail | Returns the detailed information of a businessService. | serviceDetail
get_bindingDetail | Returns detailed technical binding information for a registered service. | bindingDetail
get_tModelDetail | Returns complete tModel registration information such as the specifications used to create compatible Web Services. | tModelDetail

Let's say, for instance, that we wanted to find the business registration information of Microsoft. The following uddi_find_business.xml in Listing 7-9 is a UDDI request SOAP message containing a find_business method:

Listing 7-9 uddi_find_business.xml: A sample UDDI find_business method call.

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
  <Body>
    <find_business generic="1.0" xmlns="urn:uddi-org:api">
      <name>Microsoft</name>
    </find_business>
  </Body>
</Envelope>

As you can see, this is just a normal SOAP request message containing a find_business method with a single parameter called Name. All UDDI messages belong to the urn:uddi-org:api namespace. A sample response of this method is shown in Listing 7-10, uddi_find_business_response.xml.

Listing 7-10 uddi_find_business_response.xml: A sample find_business response.

<businessList generic="1.0" operator="Microsoft Corporation"
    truncated="false" xmlns="urn:uddi-org:api">
  <businessInfos>
    <businessInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3">
      <name>Microsoft Corporation</name>
    <description xml:lang="en">Empowering people through great
software any time, any place and on any device is Microsoft's vision.
 As the worldwide leader in software for personal and business computing,
we strive to produce innovative products and services that meet our customers. </description>
    <serviceInfos>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                        serviceKey="1FFE1F71-2AF3-45FB-B788-09AF7FF151A4">
          <name>Web services for smart searching</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="8BF2F51F-8ED4-43FE-B665-38D8205D1333">
          <name>Electronic Business Integration Services</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="611C5867-384E-4FFD-B49C-28F93A7B4F9B">
          <name>Volume Licensing Select Program</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="5DE3CE59-923E-42D3-B7FB-34FC3C3CBC16">
          <name>Technet</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="24E553C3-7E3E-484A-8ECA-80E0D0B4A91F">
          <name>Microsoft Developer Network</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="77DD86E5-CD70-4219-A28C-37231EAF3901">
          <name>Online Shopping</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="0860E130-D4AF-4BD5-9F5C-D7F6FA4B1AD8">
          <name>Home Page</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="D2BC296A-723B-4C45-9ED4-494F9E53F1D1">
          <name>UDDI Web Services</name>
        </serviceInfo>
        <serviceInfo businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                     serviceKey="A8E4999A-21A3-47FA-802E-EE50A88B266F">
          <name>UDDI Web Sites</name>
        </serviceInfo>
      </serviceInfos>
    </businessInfo>
  </businessInfos>
</businessList>

For clarity, the response is shown without the SOAP <envelope>. You can see much summary-level information returned by the find_business method, including what services are supported by the matching business entities. Armed with this data, you can now use the get methods to obtain detailed information about a particular business entity or service.

You could use, for example, the following get_serviceDetail method (Listing 7-11), found in uddi_get_servicedetail.xml, to request detailed information for a specific service identified by a serviceKey.

Listing 7-11 uddi_get_servicedetail.xml: A sample get_serviceDetail method call.

<get_serviceDetail generic='1.0' xmlns='urn:uddi-org:api'>
  <serviceKey>D2BC296A-723B-4C45-9ED4-494F9E53F1D1</serviceKey>
</get_serviceDetail>

Listing 7-12, uddi_get_servicedetail_response.xml, is the corresponding response.

Listing 7-12 uddi_get_servicedetail_response.xml: A sample get_serviceDetail response.

<serviceDetail generic="1.0" operator="Microsoft Corporation"
    truncated="false" xmlns="urn:uddi-org:api">
  <businessService businessKey="0076B468-EB27-42E5-AC09-9955CFF462A3"
                   serviceKey="D2BC296A-723B-4C45-9ED4-494F9E53F1D1">
    <name>UDDI Web Services</name>
  <description xml:lang="en">UDDI SOAP/XML message-based programmatic Web
  service interfaces.</description>
  <bindingTemplates>
    <bindingTemplate bindingKey="313C2BF0-021D-405C-8149-25FD969F7F0B"
                     serviceKey="D2BC296A-723B-4C45-9ED4-494F9E53F1D1">
      <description xml:lang="en">Production UDDI server,
      Publishing interface</description>
    <accessPoint URLType="https">https://uddi.microsoft.com/
publish</accessPoint>
    <tModelInstanceDetails>
      <tModelInstanceInfo tModelKey="uuid:64C756D1-3374-4E00-AE83-EE12E38FAE63">
        <description xml:lang="en">UDDI SOAP Publication Interface
</description>
      </tModelInstanceInfo>
      </tModelInstanceDetails>
    </bindingTemplate>
    <bindingTemplate bindingKey="A9CAFBE4-11C6-4BFE-90F5-595970D3DE24"
        serviceKey="D2BC296A-723B-4C45-9ED4-494F9E53F1D1">
      <description xml:lang="en">Production UDDI server, Inquiry interface</description>
      <accessPoint URLType="http">http://uddi.microsoft.com/inquire</accessPoint>
      <tModelInstanceDetails>
        <tModelInstanceInfo tModelKey="uuid:4CD7E4BC-648B-426D-9936-443EAAC8AE23">
          <description xml:lang="en">UDDI SOAP Inquiry Interface
</description>
        </tModelInstanceInfo>
        </tModelInstanceDetails>
      </bindingTemplate>
    <bindingTemplate bindingKey="3FE6C834-293E-4341-AF6E-41DC68949764"
                     serviceKey="D2BC296A-723B-4C45-9ED4-494F9E53F1D1">
      <description xml:lang="en">Test UDDI server, Publishing
interface</description>
      <accessPoint URLType="https">https://test.uddi.microsoft.com/publish</accessPoint>
      <tModelInstanceDetails>
        <tModelInstanceInfo tModelKey="uuid:64C756D1-3374-4E00-AE83-EE12E38FAE63">
          <description xml:lang="en">UDDI SOAP Publication
Interface</description>
        </tModelInstanceInfo>
        <tModelInstanceInfo tModelKey="uuid:F372E009-F372-429C-A09A-794113A5C5F9">
          <description xml:lang="en">urn:microsoft-com:test
signature-element signifies that this is a testing version of the service</description>
          </tModelInstanceInfo>
        </tModelInstanceDetails>
      </bindingTemplate>
    <bindingTemplate bindingKey="8ED4AD10-C63B-495E-8969-B3938F86E937"
                     serviceKey="D2BC296A-723B-4C45-9ED4-494F9E53F1D1">
      <description xml:lang="en">Test UDDI server, Inquiry
interface</description>
      <accessPoint URLType="http">http://test.uddi.microsoft.com/inquire</accessPoint>
      <tModelInstanceDetails>
        <tModelInstanceInfo tModelKey="uuid:4CD7E4BC-648B-426D-9936-443EAAC8AE23">
          <description xml:lang="en">UDDI SOAP Inquiry Interface
</description>
        </tModelInstanceInfo>
        <tModelInstanceInfo tModelKey="uuid:F372E009-F372-429C-A09A-794113A5C5F9">
          <description xml:lang="en">urn:microsoft-com:test
signature-element signifies that this is a testing version of the service</description>
          </tModelInstanceInfo>
        </tModelInstanceDetails>
      </bindingTemplate>
    </bindingTemplates>
  <categoryBag>
    <keyedReference keyName="KEYWORD" keyValue="API"
        tModelKey="uuid:A035A07C-F362-44DD-8F95-E2B134BF43B4">
</keyedReference>
    <keyedReference keyName="KEYWORD" keyValue="SOAP"
        tModelKey="uuid:A035A07C-F362-44DD-8F95-E2B134BF43B4">
</keyedReference>
    <keyedReference keyName="KEYWORD" keyValue="XML"
        tModelKey="uuid:A035A07C-F362-44DD-8F95-E2B134BF43B4">
</keyedReference>
    </categoryBag>
  </businessService>
</serviceDetail>

We see a lot of detailed information about the service we requested in these examples. The important information for us, though, is the access points that specify the URLs that can be used to invoke the associated Web Services. Finally you can obtain the associated WSDL documents of these Web Services simply by appending a ?wsdl character sequence to the end of any of these URLs.

Unfortunately, further details on the UDDI API are beyond the scope of this book. However, the UDDI Web site ( http://www.uddi.org ) is an excellent starting place to learn more about this important technology.


The BizTalk Framework Initiative

Most mature organizations have both legacy and new applications that were implemented independently and cannot easily interoperate directly because of differences in data formats, message structures, and protocols. B2B projects, such as supply chains, face equally daunting problems. Even if both suppliers and buyers shared the same enterprise systems, their data dictionaries, and other customizations probably evolved differently over time. The core problems in both scenarios are the lack of interoperable data definitions and encoding, protocols, and the knowledge of how to process the data across disparate systems.

Microsoft believes it has a solution that spans both a standard framework and a server suite of applications that help businesses describe their data and processes, as well as manage the messaging between integrated servers. This solution to some of the problems we discussed concerning EAI and B2B comes in the form of a set of schemas, guidelines, and software tools collectively called the Microsoft BizTalk Framework Initiative (BFI). Although the BFI was proposed and mostly designed by Microsoft, it is open to everyone.

The BFI consists of three main components:

  • BizTalk Framework A set of guidelines and an XML tagging scheme for the construction of readable and reliable messages.
  • BizTalk.org A community Web site that provides a standard industry schema repository and an information resource center concerning the BFI.
  • BizTalk Server A BizTalk Framework compliant software system and suite of applications for providing robust and reliable application integration using XML-based messaging.

The BizTalk Framework

One of the most difficult problems faced by businesses engaged in application integration is the lack of established message schemas for integration purposes. Traditionally businesses used the EDI for integrating applications. However, these systems are generally large and complex and usually expensive to set up and maintain. In addition, businesses might be using different operating systems, communication protocols, and programming languages. All of these factors make application integration between businesses difficult, and until recently, only big organizations have attempted integrating their business processes with trading partners. The BizTalk Framework hopes to solve these issues by providing an open framework that can be used to develop standard message schemas. The core focus of the BizTalk Framework is the definition of the BizTalk document structure.

BizTalk Documents

A BizTalk document is an extension of a SOAP version 1.1 message with BizTalk-specific SOAP header elements (called BizTags) for enhanced message-handling semantics. Instead of an RPC method body, however, a BizTalk document's <Body> element contains an application-specific and well-formed XML document containing business data. This embedded XML document is called a business document in the BizTalk Framework specification. Examples include a purchase order, invoice, product catalog, or any other business document.

Listing 7-13 shows sample_biztalk_document.xml, a sample BizTalk document. This document contains a purchase order placement request. This code is essentially a SOAP 1.1 compliant message with a header that contains BizTags and a body that contains an application-specific document.

Listing 7-13 sample_biztalk_document.xml: A sample BizTalk document.

<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
  <soap:Header>
    <eps:endpoints soap:mustUnderstand="1"
        xmlns:eps="http://schemas.biztalk.org/btf-2-0/endpoints"
        xmlns:biz="http://schemas.biztalk.org/btf-2-0/address/types">
      <eps:to>
        <eps:address xsi:type="biz:OrganizationName">ACME
</eps:address>
      </eps:to>
      <eps:from>
         <eps:address xsi:type="biz:OrganizationName">CoreXML
</eps:address>
      </eps:from>
    </eps:endpoints>
    <prop:properties soap:mustUnderstand="1"
        xmlns:prop="http://schemas.biztalk.org/btf-2-0/properties">
      <prop:identity>uuid:74b9f5d0-33fb-4a81-b02b-5b760641c1d6
</prop:identity>
      <prop:sentAt>2001-08-21T04:54:00-10:00</prop:sentAt>
      <prop:expiresAt>2001-08-22T04:54:00-10:00</prop:expiresAt>
      <prop:topic>purchase_order</prop:topic>
    </prop:properties>
    </soap:Header>
    <soap:Body>
        <po:PurchaseOrder xmlns:po="http://mspress.microsoft.com/
corexml/">
            <po:ProductID>BX0071801</po:ProductID>
        </po:PurchaseOrder>
    </soap:Body>
</soap:Envelope>
The current version of the BizTalk Framework uses the http://www.w3.org/1999/XMLSchema-instance XML Schema URI instead of the current http://www.w3c.org/2001/XMLSchema-instance because the specification was released before XML Schema became a W3C Recommendation.

BizTags

The BizTalk Framework defines four main groups of BizTags. The first group represents the source and destination of the interchange. This is specified by the <endpoints> BizTag. This BizTag is mandatory and must appear exactly once, and it consists of <to> and <from> subelements, each of which, in turn, contains a single <address> subelement.

Next the <properties> BizTag contains document identify information and other properties that describe the document. This BizTag is also mandatory and must appear exactly once. The <properties> element is also used to control the scheduling of a document through its <sentAt> and <expiresAt> subelements.

Following the <properties> element, we have the <services> element. The primary purpose of the <services> BizTag is to allow applications invoked in message interchange to request receipt acknowledgment from the consumer. The BizTalk Framework supports two kinds of receipts: the delivery receipt and the commitment receipt. Both are BizTalk documents with the addition of a special <deliveryReceipt> BizTag for conveying information related to the receipts, such as the identity of the target recipient and the expiration date.

The <manifest> BizTag serves to catalog all the contents of a multipart BizTalk message consisting of either multiple business documents or attachments that might not be XML files, such as JPEG images. A multipart BizTalk message must be encoded in a MIME structure the follows the rules of the multipart/related MIME content type.

Finally we have the <process> BizTag that provides process-management information that can help the recipient service relate this exchange with an ongoing business process. Together, all these BizTags allow BizTalk documents to be used for reliable and efficient routing message interchange.

BizTalk.org

BizTalk.org is a Web site (http://www.biztalk.org) hosted and managed by Microsoft to promote the adoption of BFI. The site has two main purposes. First it serves as an XML schema repository. Second it's a community and resource center for those building and implementing the BizTalk Framework into their services or applications.

The BizTalk.org repository aims to facilitate the initial integration efforts of potential partners by providing a fully indexed database of industry-standard and tested XML schemas. Prospective trading partners can use the repository to search for schemas by keywords or industry-standard category identifiers. Upon finding the matching schemas, BizTalk.org can help a trading partner determine whether a particular schema is relevant to the partner's need by providing additional documentation that is included with each schema in the repository.

BizTalk.org is also an information and resource center for anything related to BizTalk in particular or application integration in general. In addition, BizTalk.org provides community news and forum services. For those of you working with the BizTalk Framework, this site should definitely be a bookmark in your browser.

BizTalk Server

Microsoft's BizTalk Server is an XML-based messaging server that implements the document exchange functionality requirements laid out in the BizTalk Framework specification. Several companies are currently working on software that is BizTalk Framework compliant, but at the time of this writing the only released product is BizTalk Server. You should note that BizTalk Server goes well beyond the call of duty in this implementation, which you will soon see.

BizTalk Server is designed for both users and developers. Being a member of the Microsoft .NET Enterprise Server family of products, it provides a COM-based API for interacting with other BizTalk Servers programmatically. It also provides a rich set of GUI tools to allow users to easily manage the integration of applications. For example, BizTalk Messaging acts as a standard gateway where all low-level messaging is performed for the sending, receiving, and transformation of documents across disparate systems.

BizTalk Server provides two main categories of services: Orchestration Services and Messaging Services. We will also cover document tracking in this section.

Orchestration Services

Application integration is more than just the ability to exchange and process messages exchanged between the participants in the business process, or workflow. What must be added to have full integration is the ability to determine the logical order of actions and the corresponding flow of the messages. Traditionally businesses used modeling tools and workflow diagrams to help document the types of relationships and dependencies involved. BizTalk Orchestration Services provide a runtime engine and a design tool to allow users to design and schedule the execution of these workflows.

BizTalk Server provides a Microsoft Visio 2000-based design tool, called BizTalk Orchestration Designer that can be used to describe the business process in terms of a sequence of message exchanges. The user draws a flowchart to describe the relationship between the specific actions performed when messages are exchanged. It is also used to define the flow of data between messages. This flowchart, called a schedule, effectively documents how data flows through the business process from message to message. Orchestration Services store a schedule in an XML document that uses a Microsoft-designed schema called XLANG (pronounced "slang").

We will not get into the details of configuring and using Orchestration Services here. You will find that information in Chapter 14.

Messaging Services

BizTalk Messaging Services include receiving incoming documents, parsing and interpreting the embedded processing rules, possibly performing any necessary format transformation, and delivering the documents to their destinations. BizTalk Messaging Services also provide a mechanism for ensuring the integrity, reliability, and security of the document-interchange process. BizTalk Messaging Services are composed of a Messaging Engine and a GUI tool, called BizTalk Messaging Manager, for configuring and administering Messaging Services.

The Messaging Engine places both incoming and outgoing messages in queues. As we have learned, message queuing can greatly improve the reliability and scalability of the whole document interchange process. BizTalk Server is designed to use an external MOM system to handle the actual tasks required for supporting robust message queuing. The default MOM used by BizTalk Server is, unsurprisingly, the MSMQ.

Sending Documents to BizTalk Server

BizTalk Server provides two different methods for submitting documents to the Messaging Engine. First you can conFigure BizTalk Server to automatically pick up inbound documents found either in a user-specified MSMQ queue or a filesystem directory. Alternatively, you can programmatically submit documents to the BizTalk Server through the IInterchange COM interface. Let's take a closer look at these two methods.

Receive Functions

BizTalk Server provides a built-in facility, called receive functions, that you can use to get documents into the Messaging Engine without programming. Receive functions are components in BizTalk that can be configured to poll a specific resource for incoming documents. When a document arrives BizTalk will automatically take it off the resource and submit it to the Messaging Engine. Right out of the box, BizTalk supports two types of receive functions: file receive function and MSMQ receive function.

The file receive function monitors a user-specified file-system folder for incoming files that match a user-specified filename specification. When a matching file is found the file receive function will automatically submit it to the Messaging Engine. When the file is successfully submitted it will be removed from the folder.

The MSMQ receive function polls a user-specified MSMQ queue for incoming documents. When a document arrives it will be retrieved from the queue and submitted to the Messaging Engine. If the document is successfully submitted, the MSMQ transaction is committed and the document will be removed from the monitored queue. Otherwise, the transaction is rolled back and the document is left untouched in the queue.

The IInterchange COM Interface

In some cases the receive functions might not satisfy your requirements for submitting documents into BizTalk Server. For instance, you might have an ASP.NET application from which you want to submit documents to BizTalk directly. In cases like this, BizTalk offers a COM-based API you can use to submit your documents to BizTalk programmatically. This API is accessible through the IInterchange interface.

The IInterchange interface is simple and contains only a handful of methods. Table 7-5 summarizes the methods exposed by this interface. Note that the IInterchange interface also supports a few methods for interrogating and managing documents found in the suspended queue.

Table 7-5 The IInterchange Interface
Method | Description
Submit | Submits a document to the Messaging Engine for asynchronous processing. The submitted document is placed in a shared queue to await processing by the next available BizTalk Server in the server group. This method accepts only a string variable as the document or interchange. No object, such as COM objects, and other data types are allowed.
SubmitSync | Submits a document to the Messaging Engine and waits for a response. This method accepts only a string variable as the document or interchange. No object, such as COM objects, and other data types are allowed.
CheckSuspendedQueue | Checks the shared suspended queue and returns a list of handles to documents or interchanges in the queue that match the specified criteria.
GetSuspendedQueueItemDetails | Obtains details of a document in the suspended queue.
DeleteFromSuspendedQueue | Deletes one or more documents from the suspended queue.

Listing 7-14, submit_biztalk.aspx, is an ASP.NET application that submits a document to BizTalk using the Submit( ) method. The application first prompts you for the name of the channel (we will explain what this is in the next section) and document contents. It then submits the specified document string to the specified channel in BizTalk.

Listing 7-14 submit_biztalk.aspx: An ASP.NET application that submits a document to BizTalk.

<%@page language="vb"%>
<%
  ' retrieve submitted channel name and document string
  dim sDocument as String = "" & Request.Form( "pDocument" )
  dim sChannel as String  = "" & Request.Form( "pChannel" )
%>
<form method="post" action="submit_biztalk.asp">
  <table borders="0" cellpadding="2" cellspacing="0">
    <tr>
      <td valign="top">Channel</td>
      <td><input name="pChannel" value="<%=sChannel%>"></td>
    </tr>
    <tr>
      <td valign="top">Document</td>
      <td><textarea name="pDocument" cols="50" rows="15">
<%=sDocument%></textarea></td>
    </tr>
    <tr>
      <td></td>
      <td><input type="submit" value="Submit"></td>
    </tr>
  </table>
</form>
<%
  if ( sDocument <> "" ) then
    dim oInterchange as new Interchange
    dim sHandle as String
    ' submits a document to BizTalk for asynchronous processing
    sHandle = oInterchange.Submit( BIZTALK_OPENNESS_TYPE_NOTOPEN, sDocument,,,,,, sChannel )
  end if
%>

The BIZTALK_OPENNESS_TYPE_NOTOPEN enumeration value in the Submit( ) method call shown in Submit_BizTalk.aspx specifies that BizTalk should use a nonopen messaging port to receive the specified document. We will explain what a messaging port is in the next section.

We will not go over the details of the syntax of the Submit method and its parameters in this chapter. If you're interested you should read the BizTalk manuals or online help for further information.

Figure 7-11 shows a sample screenshot of the Submit_BizTalk.aspx application.

Receiving Documents from BizTalk Server

For applications to receive documents, BizTalk Server provides a set of transport services for delivering documents to their destinations. It offers four types of transport services:

  • File
  • MSMQ
  • Network protocols
  • COM-based Application Integration Components (AICs)

The network protocol transport services supported by BizTalk Server are standard Internet protocols including HTTP, HTTPS, and SMTP.

AICs are COM objects that implement the IBTSAppIntegration interface. The IBTSAppIntegration is an extremely simple COM interface that exposes a single method, ProcessMessage( ). AIC eliminates the need for BizTalk Server to natively understand the myriad of proprietary protocols invented by different application vendors. It also allows the application to be extensible in terms of interfacing with third-party applications. For example, instead of providing native support for SAP (a popular Enterprise Resources Manager System), an SAP AIC can be developed to allow BizTalk Server to send documents to SAP.

Figure 7-11 Running Submit_BizTalk.aspx.

The Messaging Manager

The BizTalk Messaging Manager, shown in Figure 7-12, is a GUI tool that allows the user to manage the exchange of documents by configuring the various components of Messaging Services.

Figure 7-12 The BizTalk Messaging Manager.

The Messaging Manager window is divided into two frames. The left frame provides navigational control to allow the user to select the object type and to search for objects of the selected type to manage. The right frame lists the results of the search depending on the object type. Double clicking on one of these items opens the wizard associated with the object type.

The Messaging Manager has wizards and properties dialogs for creating and configuring the objects that collectively control the operation of Messaging Services. These objects are organizations, document definitions, envelopes, messaging ports, and channels. Figure 7-13 shows the relationship between some of these objects.

Organizations

Organizations represent the trading partners with which you exchange documents. BizTalk has a special organization type, called home organization, that is used to represent your business.

Organizations have identifiers that help the Messaging Engine determine the configuration to use for processing documents. Note that BizTalk's only interest in organizations is in organizing configurations. Therefore, you can use organizations to represent departments, workgroups, or any entity that you want to have available for defining messaging configurations.

Figure 7-13 BizTalk Messaging Manager objects.

Figure 7-14 shows the Organization Properties dialog. The General tab is used to specify a name and descriptive comments for an organization. The Identifiers tab lists all the identifiers associated with an organization.

Figure 7-14 The Organization Properties dialog.

Document Definitions

A document definition is used to define a specific type of document that is processed by BizTalk. A document definition provides a pointer to a specification that, in turn, is used to define the document structure, type, and version. You will learn more about specifications in Chapter 14.

Figure 7-15 shows the Document Definition Properties dialog. It has three tabs: General, Global Tracking, and Selection Criteria.

Figure 7-15 The General tab of the Document Definitions properties dialog.

The General tab is used to define the common name and the reference to the associated document specification. For the document specification reference you must specify a WebDAV URL. You can use the Browse button to navigate and locate the appropriate WebDAV repository.

As we will see in a later section, BizTalk provides a messaging tracking capability. By default, BizTalk automatically stores certain basic information, but you can extend this to include other fields specific to your application. The Global Tracking tab, shown in Figure 7-16, lets you define the fields that you want to track.

Figure 7-16 The Global Tracking tab of the Document Definitions properties dialog.

The leftmost panel displays a tree-view representation of the fields provided in your document definition. To select a field to track you click on the appropriate button in the middle depending on the data type of the field. Once a field is selected, it will be included in the rightmost panel. This panel lists all the fields to be tracked.

The Selection Criteria tab is used for EDI documents only. It contains a list of name-value pairs that are used by BizTalk to uniquely identify the appropriate document definition for an inbound document. Please refer to the BizTalk Server manuals for further details about EDI documents and the Selection Criteria tab.

Envelopes

Envelopes are used to provide the Messaging Engine with information it needs for processing a document it carries. BizTalk supports the following six types of envelopes:

  • XML
  • ANSI X12 (an EDI format)
  • UN/EDIFACT (an EDI format)
  • Flat file (delimited and positional)
  • Reliable (BizTalk Framework)
  • Custom

Figure 7-17 shows the Envelope Properties dialog. The Envelope name is used to identify this Envelope specification when used in other configuration tasks. The Envelope format drop-down box lists the formats supported by BizTalk. When you select Custom XML, FLATFILE, or CUSTOM, you're required to specify a document specification that describes the structure of the document for which this envelope is intended.

Figure 7-17 The Envelope Properties dialog.

Messaging Ports

A messaging port can be thought of as a set of rules that trading partner organizations accept for exchanging documents. It includes destination, transport type, security, and envelope information.

You create a new messaging port by selecting the New Port menu item from the File menu. The BizTalk Messaging Manager will then display the Messaging Port wizard. The first detail you need to specify is a name and descriptive comments for this new port. When you click the Next button you will be shown a Destination Organization page similar to that shown in Figure 7-18.

You need to specify the destination organization and transport type first. For the destination you can either specify an open destination or an explicit organization by selecting from the Organization drop-down list box. An open destination is one where the destination information is determined at runtime based on the information contained in the message itself.

After that, you need to specify the primary transport the Messaging Engine will use to send the processed documents to the specified destination organization. You can use the Browse button to select from a list of available transport protocols such as HTTP, SMTP, and AIC. This option is not available for open destination.

Figure 7-18 Destination Organization page of the Messaging Port wizard.

The Service window option is used to specify an optional time window within which the transmission is restricted. Times are specified to the nearest hour using the From and To edit boxes.

Once you've completed specifying the information for the Destination Organization, clicking the Next button takes you to the Envelope Information page. This is shown in Figure 7-19.

Figure 7-19 The Envelope Information page of the Messaging Port wizard.

Here you select the envelope to associate with this messaging port. The Envelope drop-down list box lists all the envelopes known to the Messaging Manager. You can also click the New button to create a new envelope at this point.

The next and final page is the Security Information page, which is used to specify security-related information for this messaging port. This is shown in Figure 7-20.

The Encoding drop-down list box specifies the encoding type used by messages associated with this messaging port. For text messages, this is usually none. For other types, you can select either the MIME or Custom type. The latter type requires a custom encoding component. Refer to the BizTalk manuals for information about creating and using custom encoding.

The Encryption drop-down list box lets you instruct BizTalk to protect all the messages going through this messaging port by encrypting their contents using the specified encryption method. Currently BizTalk only supports the Secure Multipurpose Internet Mail Extensions (S/MIME) encryption type. You will also need to specify the digital certificate for your partner.

Channels

Channels are the primary object in Messaging Services. All the other objects we have learned about so far—organizations, document definitions, envelopes, and messaging ports—are used to support the operation of channels. A channel forms a single self-contained and named object that BizTalk uses to conFigure Messaging Services to process a document it receives.

Figure 7-20 Security Information page of the Messaging Port wizard.

Configuring channels is beyond the scope of this chapter. Please refer to the BizTalk Server manuals for further information about creating and managing channels.

Document Tracking

One detail that might be obvious is the need to track messages and documents as they flow through the system. One key advantage of BizTalk Server is that document tracking is a built-in service. Even without any programming on the user's part, BizTalk can be configured to automatically capture and display user-defined information that can be used to track the status of individual messages transiting the messaging system.

Capturing Information to Track

Metadata associated with an interchange, such as source and destination information, document type, and date and time parameters, is automatically stored and available for tracking by BizTalk Messaging Services. However, if you want to be able to track information extracted from messages submitted to BizTalk, you will need to conFigure the BizTalk Messaging Manager to capture this information.

These tracking options can be set at the following three levels:

  • Server Group
  • Channel
  • Document Definition

You've already seen how you can specify tracking options at the Document Definition level when we discussed the Document Definition object in a previous section.

For the server group tracking options you will need to start the BizTalk Administration console, select a server group, and open the associated properties dialog. This is shown in Figure 7-21.

Figure 7-21 Server group document tracking options.

Four tracking options can be configured globally for a specific server group. The Enable document tracking check box turns tracking for this server group on and off. The other three tracking options are only available when the Enable document tracking option is set.

When the Log incoming interchange and Log outgoing interchange tracking options are enabled, BizTalk will keep a copy of the input and output documents in their original native format. This is a great option for those cases when you need to fulfill legal or standard requirements or both to keep copies of all electronic business transactions.

The last option, Log the original MIME-encoded message, instructs BizTalk to store S/MIME encoded documents in the Tracking database in their original, preencoded, format.

Displaying Tracking Information

BizTalk provides a Web-based GUI tool to enable users to track documents visually. BizTalk Document Tracking provides many different search and sort options to enable the user to easily locate and format the query result for specific groups of documents that the user wishes to track.


Conclusion

We covered a lot of topics and technologies in this whirlwind chapter, but this chapter might be one of the most important in terms of concepts. We started with a discussion about the history of distributed computing and compared the different paradigms as they relate to messaging. We looked at how XML has emerged as a key enabling technology for implementing platform-independent and flexible messaging systems.

Next we became familiar with two of the most important XML-based messaging systems, namely SOAP and Web Services. With Web Services we explored in detail their implementation and deployment using tools provided through .NET and other means.

Finally we covered the BizTalk Framework Initiative and exposed you to the functionality present in the BizTalk Server, which we will cover in more detail in Chapter 14.


Creating Metadata

In this book so far we have learned how to use XML to build Web sites, create Web user interfaces, and access databases. What is it about XML that makes the technology so readily pervasive? One answer to this question is that XML is, fundamentally, portable data. In the relatively new world of distributed applications, data, not code, has become the most critical shared resource.

Modern applications need to exchange information about what services they provide, what information they need to provide those services, and how that information should be conveyed. This chapter will explore how this is accomplished. We will begin by explaining the concept of metadata and then move on to discuss how it's used in the Microsoft .NET Developer Studio. We will introduce the topic of Web Services and will use the .NET Developer Studio to create them. We will also explore the .NET Developer Studio's Object Remoting capabilities and how it uses metadata to implement remote object sharing.


Metadata and XML

Metadata is data that describes other data. Metadata about any entity, be it a programming object or a simple Web page, is information that describes that entity. Metadata and the Web have coexisted for a long time. Most Web pages you see broadcast meta-information about themselves for use in search engines. For example, the following lines of metadata appear in the header of Microsoft's Internet Information Server (IIS) 5 HTML documentation.

<META name="DESCRIPTION" content="Navigational page with links to an extensive glossary, late-breaking IIS information, installation instructions, descriptions of new features, concise quick-start
procedures for experienced server administrators, and tips for the using the IIS documentation.">
<META HTTP-EQUIV="Content-Type" content="text/html; charset=Windows-1252">
<META NAME="MS.LOCALE" CONTENT="EN-US">

This information sits behind the scenes. Although it does not appear when you look at the Web page through a browser, it's useful for providing contextual information about the page to tools such as search engines.

In this example metadata is used to convey a brief description of the page (that is, Navigational page with links to an extensive glossary...), the content-type and character set that the page uses (that is, text/html; charset=Windows-1252), and locale information (that is, EN-US).

Uses for Metadata

Uses for metadata extend beyond making your Web site more searchable on Excite.com. How else can metadata be used? In one example, plug-n-play devices have for years used metadata to broadcast hardware and driver information whenever they are installed on PCs.

All URL requests made by Web browsers send metadata about the software the user is running to browse the Web, the IP address of the user's computer, and the content-type and character encoding of the request itself. Web server software such as IIS use this information to filter requests or redirect users to pages that are more compatible with their browsers.

Transport Control Protocol (TCP) packets transmit metadata through packet headers. This information includes, among other details, the sender's IP address, the receiver's IP address, and the packet's sequence number. Audio CDs and DVDs have table-of-contents data sections that tell the player how many tracks there are, their duration, and where they begin on the disc.

What does this have to do with XML? Metadata is intrinsic to all XML documents. An XML document's elements and attributes provide extra information about the document's content. This allows people and programs to easily parse and understand information represented in XML format. We will spend the next few pages exploring XML comments, elements, and schemas and DTDs in greater depth.

Using XML for Comments

One straightforward way to include metadata in XML is by inserting comments into a transformed document. XSLT provides an <xsl:comment> element for this purpose. The XSLT processor replaces instances of this element with a well-formed comment in the result document. This form of metadata is mostly intended for human consumption.

Listing 8-1 contains a list of vegetable names and colors. In this example we will apply an XSLT the document and insert a comment along the way. Listing 8-2 shows the XSLT code behind the transformation.

Listing 8-1 vegetables.xml: A list of vegetables to be sorted and displayed.

<vegetables>
    <vegetable>
        <name>cabbage</name>
        <color>red</color>
    </vegetable>
    <vegetable>
        <name>carrot</name>
        <color>orange</color>
    </vegetable>
    <vegetable>
        <name>asparagus</name>
        <color>green</color>
    </vegetable>
    <vegetable>
        <name>squash</name>
        <color>yellow</color>
    </vegetable>
</vegetables>

Listing 8-2 vegetables.xsl: This style sheet generates a sorted HTML table with XML comments.

<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
   <xsl:template match="/vegetables">
<html>
<body>
<xsl:comment>order the vegetables by name</xsl:comment>
<table>
  <tr>
    <th>Vegetable</th>
    <th>Color</th>
  </tr>
  <xsl:apply-templates select="vegetable">
    <xsl:sort select="name"/>
  </xsl:apply-templates>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="vegetable">
  <tr>
    <td><xsl:value-of select="name"/></td>
    <td><xsl:value-of select="color"/></td>
  </tr>
</xsl:template>
</xsl:stylesheet>

The Listing 8-2 transformation, when applied to the Listing 8-1 source document, produces the following HTML source.

<html>
  <body>
  <!--order the vegetables by name-->
  <table>
    <tr>
      <th>Vegetable</th>
      <th>Color</th>
    </tr>
    <tr>
      <td>asparagus</td>
      <td>green</td>
    </tr>
    <tr>
      <td>cabbage</td>
      <td>red</td>
    </tr>
    <tr>
      <td>carrot</td>
      <td>orange</td>
    </tr>
    <tr>
      <td>squash</td>
      <td>yellow</td>
    </tr>
  </table>
  </body>
</html>

Elements

An XML document's elements are a good source of meta-information. For example, here is a record from a personnel database in the Microsoft Excel CSV format:

John,Smith,200 Brattle Street,Cambridge,MA,02138,brown,11/30/1974,blue

The meaning behind most of this data is obvious. The person's name is "John Smith" and his address is "200 Brattle Street; Cambridge, MA 02138." Unfortunately, the significance of the last three fields ("brown, 11/30/1974, blue") is less clear. Is "blue" John Smith's hair color or favorite kind of cheese?

Listing 8-3 contains the same data in XML format.

Listing 8-3 person.xml: An XML representation of a person's profile.

<person>
  <firstName>John</firstName>
  <lastName>Smith</lastName>
  <address type="home">
    <address1>200 Brattle Street</address1>
    <city>Cambridge</city>
    <state>MA</state>
    <zip>02138</zip>
  </address>
  <hairColor>brown</hairColor>
  <birthDate>11/30/1974</birthDate>
  <favoriteColor>blue</favoriteColor>
</person>

Although the actual data contained in both documents is the same, the metadata in the XML version makes it easier to understand. We were correct in guessing his name and address and the meaning of "brown", "11/30/1974", and "blue" is now evident. XML elements can also be reordered with no impact on their interpretation. The elements in the document could be ordered alphabetically (by level of hierarchy) just as well.

<person>
  <address type="home">
    <address1>200 Brattle Street</address1>
    <city>Cambridge</city>
    <state>MA</state>
    <zip>02138</zip>
  </address>
  <birthDate>11/30/1974</birthDate>
  <favoriteColor>blue</favoriteColor>
  <firstName>John</firstName>
  <hairColor>brown</hairColor>
  <lastName>Smith</lastName>
</person>

Reordering the elements has no effect on this document's meaning. The same is not true of delimited documents, where meaning is derived from arbitrary positioning. A simple swap of fields will change John Smith's hair color from brown to blue!

Schemas and DTDs

XML documents can also include a second kind of metadata in the form of a schema or a DTD. Schemas and DTDs both serve the same purpose but use different syntaxes. They carve out a language, or class, of XML documents that governs the types of elements and attributes that might appear in conformant documents. Schemas and DTDs can also impose a rigid or loose structure on the XML documents they define. For example, a <person> element must have first <firstName> and <lastName> child elements but can also have a <favoriteColor> element. Listing 8-4 is the schema that defines a person XML document. Note that a valid coreXMLPerson document need not include all of the elements defined in the schema. Our example from earlier would not be a valid codeXMLPerson document because the schema requires address.address1, address.city, address.state, and address.zip instead of address1, city, state, and zip.

Listing 8-4 PersonSchema.xml: This schema provides metadata describing the structure of a coreXMLPerson XML document.

<?xml version="1.0"?>
<Schema name="coreXMLPerson"
xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
  <ElementType name="firstName" content="textOnly" model="closed"/>
  <ElementType name="lastName" content="textOnly" model="closed"/>
  <ElementType name="birthDate" content="textOnly" model="closed"/>
  <ElementType name="hairColor" content="textOnly" model="closed"/>
  <ElementType name="favoriteColor" content="textOnly" model="closed"/>
  <ElementType name="address.address1" content="textOnly" model="closed"/>
  <ElementType name="address.address2" content="textOnly" model="closed"/>
  <ElementType name="address.address3" content="textOnly" model="closed"/>
  <ElementType name="address.city" content="textOnly" model="closed"/>
  <ElementType name="address.state" content="textOnly" model="closed"/>
  <ElementType name="address.zip" content="textOnly" model="closed"/>
  <ElementType name="address.country" content="textOnly" model="closed"/>
  <ElementType name="address" order="many" content="eltOnly" model="closed">
    <AttributeType name="type" />
    <element type="address.address1" minOccurs="0" maxOccurs="1"/>
    <element type="address.address2" minOccurs="0" maxOccurs="1"/>
    <element type="address.address3" minOccurs="0" maxOccurs="1"/>
    <element type="address.city" minOccurs="0" maxOccurs="1"/>
    <element type="address.state" minOccurs="0" maxOccurs="1"/>
    <element type="address.zip" minOccurs="0" maxOccurs="1"/>
    <element type="address.country" minOccurs="0" maxOccurs="1"/>
  </ElementType>
  <ElementType name="person" order="many" content="eltOnly" model="closed">
    <element type="firstName" minOccurs="0" maxOccurs="1"/>
    <element type="lastName" minOccurs="0" maxOccurs="1"/>
    <element type="address" minOccurs="0" maxOccurs="*"/>
    <element type="hairColor" minOccurs="0" maxOccurs="1"/>
    <element type="favoriteColor" minOccurs="0" maxOccurs="1"/>
  </ElementType>
</Schema>

We can see that this schema strictly defines all of the ingredients that go into a coreXMLPerson document. We now know what types of elements and attributes can appear inside a document of this type. We also know the structure a coreXMLPerson document must have. For instance, address information cannot appear directly beneath a <person> element: it must reside within one of many possible <address> elements.

Two types of schema definitions are in wide circulation today: XSD and the XML-Data Reduced Language (XDR). XSD is the official schema language of the W3C. XDR is an interim schema definition language the W3C offered to developers while they were drafting the XSD specification. Microsoft's MSXML 4 parser supports both types of schema definitions.

XML documents can reference their respective schemas or DTDs through the namespace attribute. To include a reference to the coreXMLPerson schema, we only need to change the root element of the Listing 8-3 document.

<person xmlns="x-schema:personschema.xml">
  ...
</person>

This tells the XML parser that a validating XDR schema can be found at the location personschema.xml. Now the person or program interpreting the XML data has more meta-information to use. We can see what other elements might appear inside similar documents. We also have a means to validate, or check the integrity of, the document we are analyzing.


Using XML for Metadata Definition

Software and hardware components have long used metadata to identify themselves when communicating. Historically this was done in a proprietary fashion where the sender and receiver first agreed on an interchange format before they could exchange metadata. XML changed this.

Today we can build applications that find and execute remote Web Services without prior knowledge of their existence. We can also publish Web Services of our own without knowing who will use those services, where they will come from, how they will use them, or even what type of client software will make the request. These services can now seamlessly exchange rich and abstract data objects.

This magic is made possible by using XML to exchange metadata. You might ask yourself, "Why all the fuss about XML? Why is a generic markup language taking over the world?" XML has many advantages that make it the ideal carrier for data and metadata on the Internet.

  1. It can be read and written by humans. This was one of the reasons for HTML's success and it works equally well for XML. This makes the language more accessible to novices and lowers the fear factor for everyone. You can see it, touch it, and use Notepad to create it. This is useful.
  2. It is open. XML is defined by the W3C. No single company owns it. Microsoft has added muscle to the W3C specification by implementing standards-compliant XML parsers and placing them in the eager hands of its developer community. Nevertheless, the language is platform-independent. UNIX clients can exchange XML data with .NET Web Services and vice versa.
  3. It is ubiquitous. XML parsers are everywhere. Getting your hands on an XML toolset and using the technology immediately is easy.
  4. It is flexible. Perhaps the most compelling reason for using XML is that it has no prescribed use. You decide how you want to use it in your application. You determine its scope, which can range from creating small XML documents to transmitting data between applications or application layers to creating your own standards body that regulates the use of XML within an entire industry.

XML is here to stay, so let's see how we can use it to transfer metadata.

Describing an Object

Object serialization is not a new concept to experienced object-oriented programmers. The idea is to take an instantiated object in memory and convert its state, or property values, into a serialized data stream that can be written to disk or transmitted over a network. The serialized version of the object must be complete enough so that the same program, or a completely different program, can reassemble it into an identical instance. As we'll see in this section, both objects and their class definitions can be serialized.

Visual C++ and Java are two examples of widely used programming languages that allow object serialization. Each language uses a proprietary data format to store the object and its metadata. This means that applications written in different languages can find trying to share serialized objects to be difficult. Not only do these applications need to understand each other's serialization format, they also need to make sense out of the serialized object's metadata to instantiate it.

Once again, XML comes to the rescue. Developers can use XML to facilitate object sharing by creating a schema that defines how objects can be serialized into platform-independent XML documents. Applications can share data by using the new object schema as a translator. Each application needs only a conduit to help marshal native objects into XML format and back again for the two to communicate.

Not all object states can be serialized. References to transient resources, such as database connections, cannot be persisted or transmitted to another program. All languages that support object serialization provide a mechanism for programmers to specify which properties should be serialized and which should not.

The class definition for the CoreXMLPerson object is contained in Listing 8-5.

Listing 8-5 person.cs: A C# class definition for a <Person> object.

using System;
namespace CSharpWebService
{
  /// <summary>
  /// A Simple Person Class Definition
  /// </summary>
  public class Person
  {
    public String firstName;
    public String lastName;
    public String birthDate;
    public String hairColor;
    public String favoriteColor;
    public Address address;
    public Person()
    {
      firstName = "";
      lastName = "";
      birthDate = "";
      hairColor = "";
      favoriteColor = "";
      address = new Address();
    }
    public Person(String _firstName, String _lastName, String _birthDate, String _hairColor, String _favoriteColor)
    {
      firstName = _firstName;
      lastName  = _lastName;
      birthDate = _birthDate;
      hairColor = _hairColor;
      favoriteColor = _favoriteColor;
      address = new Address();
    }
    public void setFirstName(String _firstName)
      { firstName = _firstName; }
    public void setLastName(String _lastName)
      { lastName = _lastName; }
    public void setBirthDate(String _birthDate)
      { birthDate = _birthDate; }
    public void setHairColor(String _hairColor)
      { hairColor = _hairColor; }
    public void setFavoriteColor(String _favoriteColor)
      { favoriteColor = _favoriteColor; }
  }
}

Our object definition declares properties for the fields, <firstName>, <lastName>, <birthDate>, <hairColor>, <favoriteColor>, and <address>. Now let's create an instance of this object in C#.

Person p = new Person("John", "Smith", "11/30/1974", "brown", "blue");

The Object Instance XML Document

This call to the Person constructor will create a Person instance in memory. If we want to save this object to a file or transmit it to another program, we must first serialize it. How do we do this? The simplest solution is to construct an Object Instance XML document that holds the state of the object in memory. This document looks astonishingly similar to a previous incarnation of this data.

<Person>
  <address type="home">
    <address1>200 Brattle Street</address1>
    <city>Cambridge</city>
    <state>MA</state>
    <zip>02138</zip>
  </address>
  <firstName>John</firstName>
  <lastName>Smith</lastName>
  <hairColor>brown</hairColor>
  <birthDate>11/30/1973</birthDate>
  <favoriteColor>blue</favoriteColor>
</Person>

Is this enough information to reconstruct the Person object in memory? Almost. Certainly, the program that originally created the object could parse the XML document and call the Person constructor method.

The Class Definition XML Document

Another program, however, would be unable to do so without more information about the Person class definition. In this case we need another XML document that describes the Person class definition. Listing 8-6 is an excerpt from such a document.

Listing 8-6 An excerpt from createPerson.wsdl, an XML Web Service descriptor, describing the Person class definition.

<s:complexType name="Person">
  <s:sequence>
    <s:element minOccurs="1" maxOccurs="1" name="firstName"
nillable="true" type="s:string" />
    <s:element minOccurs="1" maxOccurs="1" name="lastName"
nillable="true" type="s:string" />
    <s:element minOccurs="1" maxOccurs="1" name="birthDate"
nillable="true" type="s:string" />
    <s:element minOccurs="1" maxOccurs="1" name="hairColor"
nillable="true" type="s:string" />
    <s:element minOccurs="1" maxOccurs="1" name="favoriteColor"
nillable="true" type="s:string" />
    <s:element minOccurs="1" maxOccurs="1" name="address"
nillable="true" type="s0:Address" />
  </s:sequence>
</s:complexType>

Notice that all the property information in the original class definition appears in this XML document. The most important pieces of information are the property's name and data type. You might also have noticed that the address property is listed as type s0:Address. This is because the address property is a reference to another type of object named Address. This class would require its own XML descriptor as well, but it was omitted for brevity's sake.

These two XML documents, the class document and the instance document, provide enough information for another program to generate a comparable class definition and create an instance of this class with the desired state. The process of generating these stub classes and instantiating remote objects in the .NET Developer Studio will be covered more thoroughly later in the chapter.

Describing a Service

The future of distributed computing over the Internet has been hotly contested in recent years. Many object-oriented programmers believed that, one day, distributed applications would be built from objects located all over the Internet. Computers could participate in a global object community by publishing objects that could be run internally or whose code could be transmitted over the Internet. Unfortunately, firewalls, unreliable network connections, and competing technologies have all hindered this movement and have led developers to search for a more practical way to build distributed applications. In this section we will review some existing distributed technologies and then dive into the emerging distributed paradigm of Web Services.

Although these technologies differ in approach and implementation, they all share one common problem: how to describe remote methods and services so they can be discovered and used dynamically. All distributed technologies use some sort of metadata to communicate the features and semantics of the services they host. In the past the format of this metadata has often been proprietary. Today XML is used as an ideal carrier for remote service metadata.

CORBA and IIOP

When the Common Object Request Broker Architecture (CORBA) was introduced in the early 1990s, its enthusiasts speculated that fine-grained distributed objects would one day become widely available. Applications would be able to locate and use business software components via a vast network of Object Request Brokers (ORBs), and then CORBA would allow objects implemented in C++, Java, and even COBOL to be executed remotely. CORBA objects publish their behavior using the Interface Definition Language (IDL) (more metadata!) and register this information with an ORB running on a local network.

Client programs interact with these objects via stub interfaces that remotely execute an object's methods on the machine hosting the software. The ORB sits in the middle and dispatches all intermachine communication. ORBs around the Internet can share information about the objects they manage. The Internet Inter-ORB Protocol (IIOP) was developed for this purpose. CORBA supports run-time discovery of objects and is a flexible architecture for coordinating the exchanges of fine-grained software components.

JAVA RMI

Like CORBA, Java's RMI subsystem allows objects to be distributed across a network. Unlike CORBA, Java RMI can transport an entire class, software and all, across application boundaries. This is possible because all Java applications must run on the Java Virtual Machine. This enables distributed Java applications to safely download compiled byte streams and execute them locally. Today Java allows objects to be shared by either the RMI or IIOP protocols.

Although the ability to share executable objects dynamically over the Internet opens some exciting possibilities, Java RMI does have shortcomings. The technology only works if the applications involved in the exchange are both written in Java. Programs written to use RMI also require a fair amount of preparation. Remote stub classes need to be generated statically or downloaded dynamically for remote methods to work properly. Finally RMI uses a proprietary protocol to exchange information. This means more server software and firewall tuning.

Web Services—An Introduction

The push for widely distributed software components might have had more to do with the fact that we could do it, instead of whether or not we actually needed to do it. Although fine-grained distributed software components are extremely valuable at a local level, their usefulness diminishes as the scale of the network grows. Failing networks, uncooperative firewalls, and overall complexity made assembling an application from small, widely distributed pieces prohibitively difficult.

Furthermore, as E-commerce exploded on the Internet, it became apparent that data, not code, was the most precious commodity in Internet applications. The HTTP protocol had existed for years and firewalls were configured to cope with it. XML proved to be a powerful vehicle for transporting any type of information. Surely there was some way to combine the two technologies and produce the next big advance in distributed computing.

Thus, Web Services were born. Web Services are based on two simple ideas.

  1. "The need for distributed business services is greater than the need for distributed business components." What is the difference between a service and a component, anyway? A software component tends to be a fine-grained object, or set of objects, that can be embedded directly in your application. Examples include GUI widgets, API-to-desktop applications, and other tools. A service encompasses a broader interface and is more abstract. An airline reservation system is a good example of a potential Web Service. A corporate intranet application might communicate with this service over the Internet to provide convenient flight booking to the company's employees.
  2. "Web Services should be easy to access." Web Services can be accessed over the Internet using three primary methods. The first is through SOAP. SOAP enables applications to send complex objects in XML format between one another. The second method is the HTTP POST protocol. This is the same POST protocol that HTML forms use. XML name-value pairs are submitted to the Web Service and the results are received through standard output. The third way to access a Web Service is by using the HTTP GET protocol. This approach sends all of the client arguments through a URL Query string to the Web Service. The XML results are sent back on standard output. All three of these approaches use HTTP to transfer data. Therefore, no fancy client software or firewall configurations are needed to use a Web Service in your application.

Describing a Web Service

Web Services use metadata to publish themselves. The metadata is stored in Web Services Description Language (WSDL) XML documents. WSDL is a contract language that defines the messaging interface to the service. IBM and Microsoft were the chief designers of this language. The WSDL specification is maintained by the W3C. You can find it at http://www.w3.org/TR/wsdl .

Any client on the Internet can request a WSDL document with an HTTP request. The WSDL document specifies the name of the service, the names and types of the arguments it expects, the names and types of the results it returns, and directions on how to access the service (that is, SOAP message, HTTP POST, HTTP GET, or all three).

Let's look at a "HelloWorld" example that performs the valuable service of saying "Hello" to you. Imagine how much brighter our offices would be if we all used this service every morning. The HelloWorld Web Service requests only a single piece of information from you: your name. Upon receiving this data the service will present you with a happy salutation. The HelloWorld service, because it is so popular, is housed on a supercomputer somewhere directly on an Internet backbone. Say you decide to write an application that accesses this service, so you download the HelloWorld WSDL XML document. A listing of this document, with descriptions, follows.

<?xml version="1.0" encoding="utf-8" ?>
<definitions xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime=
"http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tm=
"http://microsoft.com/wsdl/mime/textMatching/" xmlns:soap=
"http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc=
"http://schemas.xmlsoap.org/soap/encoding/" xmlns:s0=
"http://tempuri.org/" targetNamespace="http://tempuri.org/"
xmlns="http://schemas.xmlsoap.org/wsdl/">

WSDL documents lead off with a host of references to different namespaces. The s namespace references the W3C's XSD Schema, with which you should be familiar by now. The http namespace identifies elements that supply HTTP connection information. The mime namespace is used to specify the MIME types of data returned from the service. The tm namespace is Microsoft's text matching namespace. This is not used in the HelloWorld example. The soap namespace is used to isolate SOAP-specific elements in the WSDL document. The soapenc namespace is used for SOAP encoding elements. The s0 namespace is used to isolate service-specific data structures. The URI for this namespace points to http://tempuri.org/, which is used only for testing purposes. In a production environment the URI would most likely reference the service vendor's domain. Finally the default namespace is set to the general WSDL namespace defined at http://schemas.xmlsoap.org/wsdl.

The types section of the WSDL document tells you the name and types of the arguments and the return result of the HelloWorld service.

  <types>
    <s:schema attributeFormDefault="qualified"
elementFormDefault="qualified" targetNamespace="http://tempuri.org/">

The <HelloWorld> element encapsulates all the incoming arguments to the service. We see here that the service expects a <complexType> argument. In this example the argument has the name name and a type of string. WSDL supports arguments more complex than strings. Service arguments can be a series of nested objects. Those object classes and subclasses must be fully described in this section of the WSDL document.

      <s:element name="HelloWorld">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="name"
nillable="true" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>

The <HelloWorldResponse> element defines the data structure the client can expect to receive from the service. We see here that the service will return a single variable of type string.

      <s:element name="HelloWorldResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name=
"HelloWorldResult" nillable="true" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="string" nillable="true" type="s:string" />
    </s:schema>
  </types>

The following section of the WSDL document describes the protocols that can be used to access the service. Notice that SOAP, GET, and POST are all covered in this section. WSDL needs to describe to potential clients the call and return semantics that the service provides. These semantics include URLs, MIME Types, and input/output message formats.

  <message name="HelloWorldSoapIn">
    <part name="parameters" element="s0:HelloWorld" />
  </message>
  <message name="HelloWorldSoapOut">
    <part name="parameters" element="s0:HelloWorldResponse" />
  </message>
  <message name="HelloWorldHttpGetIn">
    <part name="name" type="s:string" />
  </message>
  <message name="HelloWorldHttpGetOut">
    <part name="Body" element="s0:string" />
  </message>
  <message name="HelloWorldHttpPostIn">
    <part name="name" type="s:string" />
  </message>
  <message name="HelloWorldHttpPostOut">
    <part name="Body" element="s0:string" />
  </message>
  <portType name="Service1Soap">
    <operation name="HelloWorld">
      <input message="s0:HelloWorldSoapIn" />
      <output message="s0:HelloWorldSoapOut" />
    </operation>
  </portType>
  <portType name="Service1HttpGet">
    <operation name="HelloWorld">
      <input message="s0:HelloWorldHttpGetIn" />
      <output message="s0:HelloWorldHttpGetOut" />
    </operation>
  </portType>
  <portType name="Service1HttpPost">
    <operation name="HelloWorld">
      <input message="s0:HelloWorldHttpPostIn" />
    <output message="s0:HelloWorldHttpPostOut" />
    </operation>
  </portType>
  <binding name="Service1Soap" type="s0:Service1Soap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />
    <operation name="HelloWorld">
      <soap:operation soapAction="http://tempuri.org/HelloWorld" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>
  </binding>
  <binding name="Service1HttpGet" type="s0:Service1HttpGet">
    <http:binding verb="GET" />
    <operation name="HelloWorld">
      <http:operation location="/HelloWorld" />
      <input>
        <http:urlEncoded />
      </input>
      <output>
        <mime:mimeXml part="Body" />
      </output>
    </operation>
  </binding>
  <binding name="Service1HttpPost" type="s0:Service1HttpPost">
    <http:binding verb="POST" />
    <operation name="HelloWorld">
      <http:operation location="/HelloWorld" />
      <input>
        <mime:content type="application/x-www-form-urlencoded" />
      </input>
      <output>
        <mime:mimeXml part="Body" />
      </output>
    </operation>
  </binding>
  <service name="Service1">
    <port name="Service1Soap" binding="s0:Service1Soap">
      <soap:address location="http://localhost/HelloWorld/
HelloWorld.asmx" />
    </port>
    <port name="Service1HttpGet" binding="s0:Service1HttpGet">
      <http:address location="http://localhost/HelloWorld/
HelloWorld.asmx" />
    </port>
    <port name="Service1HttpPost" binding="s0:Service1HttpPost">
      <http:address location="http://localhost/HelloWorld/
HelloWorld.asmx" />
    </port>
  </service>
</definitions>


Obtaining Metadata

The metadata contained in WSDL documents is crucial information for any application that wants to use a distributed service. One problem: how do you find this information in the first place? The process of looking up a service on the Internet and obtaining its WSDL descriptor is a top-down search similar to how Domain Name Service (DNS) 6 works. Figure 8-1 illustrates the Web Service lookup procedure.

Figure 8-1 An illustration of the steps necessary to locate a Web Service.

If the location of the desired service is unknown ahead of time, you must begin your search with a registry of service providers such as UDDI. These registries are maintained by different organizations. You can query the service registry to get links to service discovery documents hosted by any listed providers of the service you requested.

The UDDI Web site ( http://www.uddi.org ) is home to many useful specifications, white papers, and best practices surrounding Web Services. The UDDI project is a cooperative effort among industry leaders. Check out this site to download the latest information on WSDL and UDDI. WSDL was submitted to W3C for consideration for further development. See http://www.w3.org/TR/wsdl .

You can download a list of specific services that a specific provider offers by following the link to the provider's discovery document. Discovery documents usually have a .disco or .vsdisco extension. The discovery document contains a detailed list of the Web Services hosted by the provider, including links to service-specific discovery documents. Listing 8-7, a service discovery document, contains a link to the service's WSDL contract.

Listing 8-7 HelloWorld.vsdico: the Hello World discovery document that points the way to the HelloWorld WSDL contract.

<?xml version="1.0" encoding="utf-8"?>
<discovery xmlns="http://schemas.xmlsoap.org/disco/">
<contractRef ref="http://localhost/HelloWorld/HelloWorld.asmx?wsdl"
docRef="http://localhost/HelloWorld/HelloWorld.asmx" xmlns=
"http://schemas.xmlsoap.org/disco/scl/" />
</discovery>

Now that you have the service definition document in hand, you're ready to begin using the HelloWorld Web Service. Let the bliss begin.


Using a Web Service

Developers can usually choose how they consume a Web Service. They can invoke the service by SOAP, GET, or POST methods. As we saw earlier, the service's WSDL contract establishes the valid communications protocols. Services generally support all three methods, however. Of the three, SOAP is the richest in terms of support for complex arguments and return results.

SOAP requests can be built and sent manually by the application developer. This is a painstaking process because all the SOAP infrastructure XML elements must be written by hand. (For a more detailed discussion on SOAP documents, see Chapter 14, "More About SOAP"). Furthermore, the programmer is responsible for posting the document to the service and unpacking the XML result. This methodology gives the developer the finest level of control. The service result document can be parsed into a proprietary data structure or forwarded to another destination.

Such low-level access to the messages, however, is tedious, error-prone, and rarely necessary. If you are planning to build an application that utilizes Web Services, you might want to look into a developer tool suite to help you with some of the plumbing. Nevertheless, examining the low-level calling syntaxes to see how it's done can be useful.

HelloWorld via SOAP

The following is an example of a SOAP request to the HelloWorld service. The first section is the HTTP request header that must be sent to the Web server. The second section consists of the SOAP envelope. The SOAP body contains the payload of the request that states the service to be called (HelloWorld) and supplies any required arguments (<name>John</name>).

POST /HelloWorld/HelloWorld.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: <length>
SOAPAction: "http://tempuri.org/HelloWorld"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <HelloWorld xmlns="http://tempuri.org/">
      <name>John</name>
    </HelloWorld>
  </soap:Body>
</soap:Envelope>

The SOAP response from the Web Service includes a similar HTTP header and SOAP envelope. This time, however, the body contains the results from the call to the service. You might notice that the request and the response are both consistent with the semantics stated in the WSDL contract document. In this case the result is nested inside a <HelloWorldResult> tag. Obviously, the syntax of the request and response documents is service-specific.

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: <length>
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <HelloWorldResponse xmlns="http://tempuri.org/">
      <HelloWorldResult>Hello, John</HelloWorldResult>
    </HelloWorldResponse>
  </soap:Body>
</soap:Envelope>
Microsoft was the primary sponsor of the SOAP specification. They released version 0.9 of the specification in 1999. The W3C released version 1.1 in May 2000. Both parties kept the protocol simple, which is one of the reasons why it was so successful. SOAP version 1.2 is under active development at W3C. For more information, see http://www.w3.org/TR/soap12/ .

HelloWorld via HTTP GET

The HTTP GET method is the simplest way to invoke a Web Service. All of the argument data to the service is passed inside the URL string in URLEncoded format. This request string shows how it's done for HelloWorld:

GET /HelloWorld/HelloWorld.asmx/HelloWorld?name=John HTTP/1.1
Host: localhost

The Web Service cannot return a SOAP document because the request was not made using SOAP. Therefore, the service must return a simple XML document in the HTTP response with the result of the function call.

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://tempuri.org/">Hello, John</string>

HelloWorld via HTTP POST

HTTP POST differs from HTTP GET for Web Service invocations the same way it differs in HTML Form posts. All the arguments to the service are stored in the HTTP message body as name-value pairs instead of inside the URL string. This approach is more secure and robust than HTTP GET but not as flexible as using SOAP. The HTTP POST request to the HelloWorld service follows.

POST /HelloWorld/HelloWorld.asmx/HelloWorld HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: <length>
name=John

The HTTP POST response document is identical to the HTTP GET response document. The response is formatted in plain XML without any SOAP references.

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://tempuri.org/">John</string>


.NET Support for Metadata and Web Services

Microsoft designed its .NET Developer Studio to be friendly to Web Service authors and consumers. It offers a rich set of tools, wizards, and utility classes to automate the discovery and execution of Web Services.

The .NET Developer Studio enables service developers to quickly build Web Services that can be deployed on either local or remote Web servers. The .NET Developer Studio automates the creation of all the discovery and WSDL contracts. It even creates simple Web documents that help you test and debug your code. All you have to do is write the business logic! The studio also includes a rich API that allows programmers to make high-level calls to remote services. The utilities handle all of the conversion work to and from SOAP messages.

Microsoft is not the only company touting the future of Web Services. Sun is actively working on integrating the idea in their Java 2 Enterprise Edition. This means that Microsoft and Sun Web Services will one day be able to work together, side-by-side!

Think back to our friend John Smith. His company's HR Director has begun a project to reimplement their personnel management system using Web Services. The plan is to use the .NET Developer Studio to create both the personnel management services and clients that access them.

The project team begins by implementing a service that will create new employees in their back-office database. The Web Service will expose a simple createPerson( ) Web method that accepts essential employee information, creates an entry for the person in the employee database, and returns a SOAP document with all the person's properties filled out. In this section we will look more closely at the createPerson( ) Web Service.

The createPerson( ) Web Service

The createPerson( ) Web Service will be implemented in the C# programming language. The ASP .NET Web Service Project wizard initializes most of the infrastructure necessary to create a Web Service. Among other items, it creates the .vsdisco and WSDL files (the WSDL contract is actually generated dynamically by a call to the .asmx service file) that hold all the metadata clients will need to locate and access the service. The .vsdisco file contains links to related WSDL contracts. The wizard also creates a template .asmx file that is the starting point from where coding the service's business logic begins.

Before they can begin coding the service the team needs to add a few classes to the project. The first class defines a Person object and the second defines an Address object. The code behind the Person class is similar to the class definition shown earlier in the chapter, with the exception of address information. Listing 8-8 shows the file.

Listing 8-8 person.cs: A C# source file that contains the Person class definition.

using System;
namespace CSharpWebService
{
  /// <summary>
  /// A Simple Person Class Definition
  /// </summary>
  public class Person
  {
    public String firstName;
    public String lastName;
    public String birthDate;
    public String hairColor;
    public String favoriteColor;
    public Address address;
    public Person()
    {
      firstName = "";
      lastName = "";
      birthDate = "";
      hairColor = "";
      favoriteColor = "";
      address = new Address();
    }
    public Person(String _firstName, String _lastName, String _birthDate,
String _hairColor, String _favoriteColor)
    {
      firstName = _firstName;
      lastName  = _lastName;
      birthDate = _birthDate;
      hairColor = _hairColor;
      favoriteColor = _favoriteColor;
      address = new Address();
    }
    public void setFirstName(String _firstName){ firstName = _firstName; }
    public void setLastName(String _lastName){ lastName = _lastName; }
    public void setBirthDate(String _birthDate) { birthDate = _birthDate; }
    public void setHairColor(String _hairColor) { hairColor = _hairColor; }
    public void setFavoriteColor(String _favoriteColor)
{ favoriteColor = _favoriteColor; }
    public void setAddress(String address1, String address2, String city,
      String state, String postalCode, String country)
    {
      address.setAddress1(address1);
      address.setAddress2(address2);
      address.setCity(city);
      address.setState(state);
      address.setPostalCode(postalCode);
      address.setCountry(country);
    }
  }
}

The properties listed in the Listing 8-8 class definition are basic personnel information: the employee's name, hair color, date of birth, favorite color, and home address. The properties need to be marked public so they can be returned via a SOAP document. The class's methods are simple accessors that manipulate the classes properties. The setAddress( ) method sets the properties on the address dependent object.

The designers of the system decided to create a helper Address class to provide common API for storing addresses. This class might be extended later to support driving directions or other features. The class, as it exists now, is straightforward. It contains a handful of relevant properties and the accessor methods required to manipulate them. The class definition for the address class is contained in Listing 8-9.

Listing 8-9 address.cs: A C# source file containing the Address class definition.

using System;
namespace CSharpWebService
{
  /// <summary>
  /// A simple address object
  /// </summary>
  public class Address
  {
    public String address1;
    public String address2;
    public String address3;
    public String city;
    public String state;
    public String postalCode;
    public String country;
    public Address()
    {
      address1 = "";
      address2 = "";
      address3 = "";
      city = "";
      state = "";
      postalCode = "";
      country = "";
    }
    public String getAddress1() { return address1; }
    public String getAddress2() { return address2; }
    public String getAddress3() { return address3; }
    public String getCity() { return city; }
    public String getState() { return state; }
    public String getPostalCode() { return postalCode; }
    public String getCountry() { return country; }
    public void setAddress1(String _address1){ address1 = _address1;}
    public void setAddress2(String _address2){ address2 = _address2;}
    public void setAddress3(String _address3){ address3 = _address3;}
    public void setCity(String _city){city = _city;}
    public void setState(String _state){state = _state;}
    public void setPostalCode(String _postalCode){postalCode = _postalCode;}
    public void setCountry(String _country){country = _country;}
  }
}

Now that the underlying objects have been built the development team can begin developing the Web Service logic. Most of the code in this file was generated by the .NET Web Service Wizard. The only code the programmers need to worry about comes after the [WebMethod] C# declaration. Listing 8-10 implements our Web Service.

Listing 8-10 Service1.asmx.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
namespace CSharpWebService
{
  /// <summary>
  /// Summary description for Service1.
  /// </summary>
  public class Service1 : System.Web.Services.WebService
  {
    public Service1()
    {
      //CODEGEN: This call is required by the ASP.NET Web Services Designer
      InitializeComponent();
    }
    #region Component Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
    }
    #endregion
    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
    }
    [WebMethod]
    public Person CreatePerson(String firstName, String lastName,
      String birthDate, String hairColor, String favoriteColor, String address1,
String address2, String city, String state, String postalCode, String country)
    {
      Person p = new Person(firstName,lastName, birthDate, hairColor, favoriteColor);
      p.setHomeAddress(address1,address2,city,state,postalCode,country);
         // Database logic goes here (this code has been omitted for brevity)
      return p;
    }
  }
   }

Notice that the code following the [WebMethod] declaration is nothing special. The CreatePerson( ) function looks normal in every respect. It doesn't need any special code to retrieve its arguments from a SOAP or HTTP document. It can create objects, load COM components, and access databases just like any other program. Finally the function is free to return a complex object as its result without worrying about creating a SOAP response on the way out. The .NET Framework handles all this hassle for the developer, so much so that the Web Service has no idea that it is being accessed remotely!

Once the service is compiled and deployed (.NET also automates the deployment process), it's ready to be accessed by any type of remote client. Before we write the client, let's first take a step back and look at the metadata the service has to offer. As mentioned earlier, one of the files the wizard created was the Listing 8-7 .vsdisco discovery document. The wizard also placed a reference to this file in the Listing 8-11 discovery file in the root directory of the Web server. These files are listed here.

Listing 8-11 Default.vsdisco: The global discovery file for the Web server that provides links to service-specific discovery documents.

<?xml version="1.0" encoding="utf-8"?>
<discovery xmlns="http://schemas.xmlsoap.org/disco/">
    <discoveryRef    ref="http://localhost/CSharpWebService/
CSharpWebService.vsdisco" />
    <discoveryRef ref="http://localhost/HelloWorld/
HelloWorld.vsdisco" />
</discovery>

Notice that one of the discovery references in this file points to the URL http://localhost/CSharpWebService/CSharpWebService.vsdicso. Listing 8-12 contains the discovery information for the service we just wrote. Here are the contents of this file.

Listing 8-12 SharpWebService.vsdisco: The createPerson service discovery file.

<?xml version="1.0" encoding="utf-8"?>
<discovery xmlns="http://schemas.xmlsoap.org/disco/">
  <contractRef ref="http://localhost/CSharpWebService/Service1.asmx?wsdl"
docRef="http://localhost/CSharpWebService/Service1.asmx" xmlns=
"http://schemas.xmlsoap.org/disco/scl/" />
</discovery>

Here we are finally redirected to the services WSDL contract. The ref attribute of the <contactRef> element contains a URL to this resource ("http://localhost/CSharpWebService/Service1.asmx?wsdl"). As mentioned earlier, the WSDL contract holds the keys to accessing the Web Service. The metadata contained in this WSDL document is a more complex than the one shown in our HelloWorld example earlier. The complete WSDL document can be found in Listing 8-13.

Listing 8-13 createPerson.wsdl: The createPerson WSDL contract.

<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime=
"http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tm=
"http://microsoft.com/wsdl/mime/textMatching/" xmlns:soap=
"http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc=
"http://schemas.xmlsoap.org/soap/encoding/" xmlns:s0=
"http://tempuri.org/" targetNamespace=
"http://tempuri.org/" xmlns=
"http://schemas.xmlsoap.org/wsdl/">
  <types>
    <s:schema attributeFormDefault="qualified"
elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
      <s:element name="CreatePerson">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name=
"firstName" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"lastName" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"birthDate" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"hairColor" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"favoriteColor" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"address1" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"address2" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"city" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"state" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"postalCode" nillable="true" type="s:string" />
            <s:element minOccurs="1" maxOccurs="1" name=
"country" nillable="true" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="CreatePersonResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name=
"CreatePersonResult" nillable="true" type="s0:Person" />
          </s:sequence>
        </s:complexType>
      </s:element>

The primary difference between this WSDL document and the simpler HelloWorld version is that the return type of the createPerson( ) service is a complex object rather than a string. An <s:element> tag is created for each property in the class. The first five properties are all of type string and require no further definition. The homeAddress property, however, is an Address object. The contract must also include a <complexType> definition for Address if a client wants to reference it. The Address class is defined next. Because none of its properties are complex objects, the WSDL document is finished describing classes.

      <s:complexType name="Person">
        <s:sequence>
          <s:element minOccurs="1" maxOccurs="1" name="firstName"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="lastName"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="birthDate"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="hairColor"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="favoriteColor"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="homeAddress"
nillable="true" type="s0:Address" />
        </s:sequence>
      </s:complexType>
      <s:complexType name="Address">
        <s:sequence>
          <s:element minOccurs="1" maxOccurs="1" name="address1"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="address2"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="address3"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="city"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="state"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="postalCode"
nillable="true" type="s:string" />
          <s:element minOccurs="1" maxOccurs="1" name="country"
nillable="true" type="s:string" />
        </s:sequence>
      </s:complexType>
      <s:element name="Person" nillable="true" type="s0:Person" />
    </s:schema>
  </types>

The SOAP, GET, and POST request and response documents are similar in nature to those defined in the HelloWorld example. Notice that the CreatePersonXXXOut all return some form of the s0:Person complexElement type declared previously.

  <message name="CreatePersonSoapIn">
    <part name="parameters" element="s0:CreatePerson" />
  </message>
  <message name="CreatePersonSoapOut">
    <part name="parameters" element="s0:CreatePersonResponse" />
  </message>
  <message name="CreatePersonHttpGetIn">
    <part name="firstName" type="s:string" />
    <part name="lastName" type="s:string" />
    <part name="birthDate" type="s:string" />
    <part name="hairColor" type="s:string" />
    <part name="favoriteColor" type="s:string" />
    <part name="address1" type="s:string" />
    <part name="address2" type="s:string" />
    <part name="city" type="s:string" />
    <part name="state" type="s:string" />
    <part name="postalCode" type="s:string" />
    <part name="country" type="s:string" />
  </message>
  <message name="CreatePersonHttpGetOut">
    <part name="Body" element="s0:Person" />
  </message>
  <message name="CreatePersonHttpPostIn">
    <part name="firstName" type="s:string" />
    <part name="lastName" type="s:string" />
    <part name="birthDate" type="s:string" />
    <part name="hairColor" type="s:string" />
    <part name="favoriteColor" type="s:string" />
    <part name="address1" type="s:string" />
    <part name="address2" type="s:string" />
    <part name="city" type="s:string" />
    <part name="state" type="s:string" />
    <part name="postalCode" type="s:string" />
    <part name="country" type="s:string" />
  </message>
  <message name="CreatePersonHttpPostOut">
    <part name="Body" element="s0:Person" />
  </message>
  <portType name="Service1Soap">
    <operation name="CreatePerson">
      <input message="s0:CreatePersonSoapIn" />
      <output message="s0:CreatePersonSoapOut" />
    </operation>
  </portType>
  <portType name="Service1HttpGet">
    <operation name="CreatePerson">
      <input message="s0:CreatePersonHttpGetIn" />
      <output message="s0:CreatePersonHttpGetOut" />
    </operation>
  </portType>
  <portType name="Service1HttpPost">
    <operation name="CreatePerson">
      <input message="s0:CreatePersonHttpPostIn" />
      <output message="s0:CreatePersonHttpPostOut" />
    </operation>
  </portType>
  <binding name="Service1Soap" type="s0:Service1Soap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />
    <operation name="CreatePerson">
      <soap:operation soapAction="http://tempuri.org/CreatePerson" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>
  </binding>
  <binding name="Service1HttpGet" type="s0:Service1HttpGet">
    <http:binding verb="GET" />
    <operation name="CreatePerson">
      <http:operation location="/CreatePerson" />
      <input>
        <http:urlEncoded />
      </input>
      <output>
        <mime:mimeXml part="Body" />
      </output>
    </operation>
  </binding>
  <binding name="Service1HttpPost" type="s0:Service1HttpPost">
    <http:binding verb="POST" />
    <operation name="CreatePerson">
      <http:operation location="/CreatePerson" />
      <input>
        <mime:content type="application/x-www-form-urlencoded" />
      </input>
      <output>
        <mime:mimeXml part="Body" />
      </output>
    </operation>
  </binding>
  <service name="Service1">
    <port name="Service1Soap" binding="s0:Service1Soap">
      <soap:address location="http://localhost/CSharpWebService/Service1.asmx" />
    </port>
    <port name="Service1HttpGet" binding="s0:Service1HttpGet">
      <http:address location="http://localhost/CSharpWebService/Service1.asmx" />
    </port>
    <port name="Service1HttpPost" binding="s0:Service1HttpPost">
      <http:address location="http://localhost/CSharpWebService/Service1.asmx" />
    </port>
  </service>
</definitions>

The createPerson( ) Web Service Client

We will begin writing our client by examining the metadata that our new service will accept and return. We will stick to examples using the SOAP invocation protocol. To reconstruct John Smith's record in the new personnel system we will need to send a SOAP document with the following format to the createPerson( ) method.

POST /CSharpWebService/Service1.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/CreatePerson"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap=
"http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <CreatePerson xmlns="http://tempuri.org/">
      <firstName>John</firstName>
      <lastName>Smith</lastName>
      <birthDate>11/30/1974</birthDate>
      <hairColor>brown</hairColor>
      <favoriteColor>blue</favoriteColor>
      <address1>200 Brattle Street</address1>
      <address2></address2>
      <city>Cambridge</city>
      <state>MA</state>
      <postalCode>02138</postalCode>
      <country>USA</country>
    </CreatePerson>
  </soap:Body>
</soap:Envelope>

We could create this document by hand and ship it off to the Web server, but why go through the effort when .NET includes utilities to help do it for us? One such utility is the SoapHttpClientProtocol class. This class provides a layer of abstraction above the underlying SOAP protocol. Developers can use this API to call remote Web Services as if they were local methods.

This class can be used only through sub-classes. You must create your own class that extends SoapHttpClientProtocol and adapt it for a specific Web Service. To implement a sub-class of SoapHttpClientProtocol you must perform at least four tasks.

  1. Provide a default constructor for the class that sets the Url property of the superclass to the location of the Web Service (as defined in the service's WSDL contract).
  2. Create a method in the class that has the same signature as the method in the Web Service.
  3. Call the Invoke method of the SoapHttpClientProtocol class, passing in the service name and an array of objects representing the service arguments.
  4. Provide local class definitions for any complex objects that are returned by the service.

The following code listing shows an example of a SoapHttpClientProtocol sub-class engineered to communicate with the CreatePerson service we created earlier. The file that contains this class definition is Listing 8-14.

Listing 8-14 PersonCreator.cs: A utility class that provides high-level access to the CreatePerson service.

using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.Web.Services;

The WebServiceBindingAttribute attribute states that the PersonCreator class contains one or more Web methods that implement the Service1Soap binding, as defined by the WSDL contract for the Web Service. Bindings are similar to interfaces in that they prescribe a strict set of operations. Classes that implement one or more bindings for a Web Service need to make sure that they implement all operations defined within that binding. Let's review the Service1Soap binding that we saw at the beginning of this section.

  <binding name="Service1Soap" type="s0:Service1Soap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />
    <operation name="CreatePerson">
      <soap:operation soapAction="http://tempuri.org/CreatePerson" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>
  </binding>

The binding implies that the class will contain a CreatePerson( ) Web Service method that implements some sort of SOAP action. The corresponding C# Web method, shown here, uses a C# attribute to identify itself.

[System.Web.Services.WebServiceBindingAttribute(Name="Service1Soap",
Namespace="http://tempuri.org/")]
public class PersonCreator : System.Web.Services.Protocols.
SoapHttpClientProtocol
{
  [System.Diagnostics.DebuggerStepThroughAttribute()]
  public PersonCreator()
  {
    this.Url = "http://localhost/CSharpWebService/Service1.asmx";
  }
  [System.Diagnostics.DebuggerStepThroughAttribute()]

A call to a Web Service method, or operation, can be encoded in two ways: RPC and Document. The RPC style encodes the Web Service method using the SOAP for RPC specification. The Document style, which is used here as indicated by the SoapDocumentMethodAttribute attribute, creates a SOAP XML message that is formatted exactly as defined by the XSD schema contained in the service's WSDL document.

The method signature to CreatePerson( ) also follows the directions of the WSDL document in its arguments and return type.

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/CreatePerson",
Use=System.Web.Services.Description.
SoapBindingUse.Literal, ParameterStyle=System.Web.Services.
Protocols.SoapParameterStyle.Wrapped)]
  public Person CreatePerson(string firstName, string lastName, string birthDate, string hairColor,
string favoriteColor, string address1, string address2, string city, string state, string postalCode,
string country)
  {

The magic begins with a call to the SoapHttpClientProtocol class's Invoke( ) method. The .NET infrastructure marshals the argument object (an array of String objects) into an XML SOAP document, sends the document to the Web Service, waits for the service to reply, and unmarshals the return result into position zero of the result array. This result is cast to a Person object and is returned to the client.

    object[] results = this.Invoke("CreatePerson", new object[] {
                                    firstName,
                                    lastName,
                                    birthDate,
                                    hairColor,
                                    favoriteColor,
                                    address1,
                                    address2,
                                    city,
                                    state,
                                    postalCode,
                                    country});
    return ((Person)(results[0]));
  }
}

Local definitions for the Person and Address classes are needed to cast the return result without error. Once again, the WSDL contract has all the information you need to create these class definitions. In fact, these Person and Address class definitions don't necessarily need to match those found in the Web Service. Only the publicly accessible properties (that is, those properties marked as public or accessible through a getter/setter design pattern) need to appear in the client's code.

public class Person
{
  public string firstName;
  public string lastName;
  public string birthDate;
  public string hairColor;
  public string favoriteColor;
  public Address homeAddress;
}
public class Address
{
  public string address1;
  public string address2;
  public string address3;
  public string city;
  public string state;
  public string postalCode;
  public string country;
}

The service proxy that we just built provides an additional layer of abstraction for our client. We now have a business-level interface to the PersonCreator Web Service. Client programs that use the PersonCreator class do not need to worry about where the Web Service is located or what protocol is used to invoke it. As you can see here, the PersonCreator class is easy to use. You'd never know there was so much complexity—and metadata—underneath the covers.

PersonCreator personCreator = new PersonCreator();
Person p = personCreator.CreatePerson("John", "Smith", "11/30/1974",
"brown", "blue", "200 Brattle Street", "", "Cambridge", "MA", "02138", "USA");

This is a slick programming interface. Microsoft's SoapHttpClientProtocol class saved us some time by masking the gory details of SOAP messaging. Still, the process of extending this class every time we want to use a Web Service can become tedious. If this were the only way to work, each time you wanted to use a Web Service you'd have to download its WSDL contract, interpret it (which can be tricky, especially when multiple classes are involved!), and manually create a stub class that implements all of the defined operations. Fortunately, the .NET provides tools to do all this for you.


Generating a Client Proxy from XML Metadata

Microsoft provides a few tools in its .NET Framework to aid in developing client proxies to access Web Services. The WSDL executable, located in the bin directory of the framework SDK, does all the dirty work for you. The syntax for running wsdl.exe from the command line is as follows:

Wsdl /language:language  /protocol:protocol /namespace:myNameSpace /
out:filename /username:username /password:password /domain:domain <url or path>

Table 8-1 documents the WSDL.exe command's arguments.

Table 8-1 WSDL Command Syntax
Parameter Value

<url or path>

A URL or path to a service description, a file describing a Web Service in WSDL. If you specify a file, supply a file containing the service description. For example:

myWebservice.wsdl

If you specify a URL, the URL needs to reference an .asmx page or return a service description. For ASP.NET Web Services you can return a service description by appending WSDL to the URL of the Web Service. For example,

http://www.contoso.com/MyWebService.asmx?WSDL

/language:language

(Optional) The language the proxy class is generated in. Available options include CS, VB, and JS, referring to C#, Visual Basic.NET, and JScript.NET, respectively. If no language option is specified, the proxy class is generated in C#.

/protocol:protocol

(Optional) The protocol used to invoke the Web Service methods. Available options include SOAP, HttpGET, and HttpPOST. If no protocol is specified, SOAP is the default.

/namespace:
myNameSpace

(Optional) The namespace of the generated proxy. Default value is the global namespace.

/out:filename

(Optional) The name of the file to create containing the proxy class. Default name is based on the name of the Web Service.

/username:username

(Optional) The username to use when connecting to a Web server that requires authentication.

/password:password

(Optional) The password to use when connecting to a Web server that requires authentication.

/domain:domain

(Optional) The domain to use when connecting to a Web server that requires authentication.

Let's use this tool on our CreatePerson Web Service.

wsdl /language:CS /out:PersonCreator.cs http://localhost/
CSharpWebService/Service1.asmx?WSDL

The tool created the Listing 8-15 file that contains all the proxy code needed to access the service. This file can be imported into our client .NET project and used out of the box. Here is the code that the WSDL.exe utility generated for us.

Listing 8-15 PersonCreator.cs: A C# source file containing the proxy code required to access the createPerson( ) Web Service method.

//------------------------------------------------------------------
// <autogenerated>
//   This code was generated by a tool.
//   Runtime Version: 1.0.2914.11
//
//   Changes to this file may cause incorrect behavior and will be lost if
//   the code is regenerated.
// </autogenerated>
//-----------------------------------------------------------------
//
// This source code was auto-generated by wsdl, Version=1.0.2914.11.
//
using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.Web.Services;
[System.Web.Services.WebServiceBindingAttribute
(Name="Service1Soap", Namespace="http://tempuri.org/")]
public class Service1 : System.Web.Services.Protocols.
SoapHttpClientProtocol {
  [System.Diagnostics.DebuggerStepThroughAttribute()]
  public Service1() {
    this.Url = "http://localhost/CSharpWebService/Service1.asmx";
  }
  [System.Diagnostics.DebuggerStepThroughAttribute()]
  [System.Web.Services.Protocols.SoapDocumentMethodAttribute
("http://tempuri.org/CreatePerson", Use=System.Web.Services.
Description.SoapBindingUse.Literal, ParameterStyle=System.Web.
Services.Protocols.SoapParameterStyle.Wrapped)]
  public Person CreatePerson(string firstName, string lastName,
string birthDate, string hairColor, string favoriteColor, string address1,
string address2, string city, string state,
string postalCode, string country) {
    object[] results = this.Invoke("CreatePerson", new object[] {
          firstName,
          lastName,
          birthDate,
          hairColor,
          favoriteColor,
          address1,
          address2,
          city,
          state,
          postalCode,
          country});
    return ((Person)(results[0]));
  }
  [System.Diagnostics.DebuggerStepThroughAttribute()]
  public System.IAsyncResult BeginCreatePerson(string firstName,
string lastName, string birthDate, string hairColor, string
favoriteColor, string address1, string address2, string city, string state,
string postalCode, string country, System.AsyncCallback
callback, object asyncState) {
    return this.BeginInvoke("CreatePerson", new object[] {
          firstName,
          lastName,
          birthDate,
          hairColor,
          favoriteColor,
          address1,
          address2,
          city,
          state,
          postalCode,
          country}, callback, asyncState);
  }
  [System.Diagnostics.DebuggerStepThroughAttribute()]
  public Person EndCreatePerson(System.IAsyncResult asyncResult) {
    object[] results = this.EndInvoke(asyncResult);
    return ((Person)(results[0]));
  }
}
public class Person {
  public string firstName;
  public string lastName;
  public string birthDate;
  public string hairColor;
  public string favoriteColor;
  public Address address;
}
public class Address {
  public string address1;
  public string address2;
  public string address3;
  public string city;
  public string state;
  public string postalCode;
  public string country;
}

You might notice that this file looks similar to the PersonCreator class shown in Listing 8-14. One significant difference is the introduction of two new methods: BeginCreatePerson( ) and EndCreatePerson( ). These functions, generated by the WSDL.exe utility, allow you to invoke the CreatePerson service asynchronously.

.NET supports a design pattern that implements asynchronous Web method invocation, regardless of how the Web method was written. The pattern prescribes two new asynchronous methods, Begin( ) and End( ), for every synchronous method. These functions give non-blocking access to Web Services that take a long time to complete. To call the function asynchronously you must alter your client to call BeginCreatePerson( ) instead of CreatePerson( ). BeginCreatePerson( ) kicks off the Web Service but doesn't wait for it to finish. Instead it returns a handle to an object that implements the IAsyncResult interface. The IAsyncResult interface contains a WaitHandle object that handles all of the synchronization involved in waiting for the Web Service to complete.

Once the CreatePerson service has completed, you must call the EndCreatePerson( ) function to retrieve the results. The call to EndCreatePerson( ) can happen via a callback function passed into BeginCreatePerson( ), or you can wait on the WaitHandle object explicitly and call EndCreatePerson( ) when the service has finished its job. EndCreatePerson( ) requires you to pass in the IAsyncResult handle as an argument. It returns the result the same way the synchronous method does.

Visual Studio .NET will automate the creation of Web Service proxies for you. By adding a "Web reference" to your project you can dynamically generate all the code you need to access a particular Web Service.


An Overview of .NET Remoting

As we have seen, the .NET makes generous use of XML metadata. From XML comments to remote Web Service discovery and execution, the .NET uses metadata to make software components more self-descriptive. This promotes loose, run-time coupling of objects and applications in a development environment rich with opportunity.

Inter-application object sharing need not be limited to Web Services. The .NET Remoting infrastructure encompasses many different levels of interobject and interapplication communication. In this section we will cover the .NET platform's support for distributed objects. We will touch on how Web Services relate to remotable objects. We will then explore some fine-grained remote programming techniques that the .NET platform also provides. This discussion will include conceptual overviews as well as detailed code samples.

Remote Web Services

Remote Web Services are ideal for publishing course-grained application methods to the widest possible consumer audience. Because Web Services rely exclusively on open, ubiquitous standards such as HTTP, SOAP, and XML for communication, they are accessible to any type of Internet application.

Web Services are also useful for publishing software to an unknown user base. A good example of a Web Services candidate is our Noverant.com online CD store. As its volume increases, Noverant might want to consider exposing its CD purchasing business logic through a Web Service. This would allow partners and customers to create orders directly through software, bypassing a tedious Web site. Custom Noverant Web Service clients, built in a .NET studio, Notepad, or any other development toolset, could be written to browse and purchase items from the Noverant CD catalog.

Web Services are fantastic, but when do you not want to use them? Unfortunately, Web Services do have a few limitations. All Web Service objects are passed by value through SOAP. Pass-by-reference is not supported. A certain amount of overhead is associated with calls to Web Services. Objects need to be marshaled into (potentially large) XML SOAP documents and return results need to be unmarshaled. Furthermore, reliance on HTTP makes Web Services stateless in nature. Web Service connections are closed upon the completion of any call to a Web method.

These limitations make Web Services less suitable for fine-grained object sharing. Fortunately, the .NET Common Language Runtime (runtime) engine has support for a wide variety of distributed object sharing and messaging. The following feature list is an excerpt from the .NET Framework Developer's Guide.

  • The ability to publish and consume services and objects across a wide variety of application types. Applications that can participate in remote services include console applications, Microsoft Windows GUI applications, IIS-based Web applications, ASP .NET Web Services, or local Windows services.
  • The preservation of full managed-code type-system fidelity.
  • The ability to pass objects by reference and to return to a particular object in a particular application domain.
  • Binary Object Serialization in addition to SOAP XML-based serialization.
  • The ability to control activation characteristics and object lifetimes directly.
  • The ability to implement and use third-party channels or protocols to extend communication to meet your specific needs.
  • Direct participation in the communication process to create the functionality you need.

.NET Remoting Overview

The .NET Remoting framework provides tools that allow applications to share objects and data over a network. Web Services are just one aspect of .NET Remoting. Trusted client applications might need to interact with server-side software components at the object level rather than the service level. The .NET Remoting framework provides these services and many others. Let's look at how the .NET Remoting framework relates to applications, messages, objects, and channels.

Applications, Messages, and Objects

Applications form loosely coupled communities in the .NET Remoting framework. They can communicate with each other at one of two levels.

  1. They can send high-level messages. These messages can contain different kinds of application data, including binary formats, XML text, plain text, or HTML.
  2. They can share actual objects with each other. The two applications do not need to be homogeneous in platform to communicate at the object level. Participating programs can be written in .NET C#, VB, Java, C, or COBOL. SOAP is used to promote loose coupling between applications at the object level. SOAP messages can be sent anywhere over HTTP and make ideal carriers for pass-by-value objects.

Applications that participate in remote transactions are isolated from one another in the .NET by Application Domains. One or more applications might reside inside a single application domain. The AppDomain class manages these entities. Its methods enable the runtime to load assemblies or threads into the domain, define dynamic assemblies, specify loading and termination events, and terminate the domain.

Channels

SOAP and other interapplication messages are transported through channels. Channels relay information between application domains, processes, and machines. Channels can either listen for new incoming messages or relay outbound messages to a destination channel. Channels are also responsible for converting messages into different wire formats. For example, the TcpChannel class converts outbound messages into a compact binary format and converts incoming messages from binary into their original format.

Channels implement the IChannel interface that defines the properties ChannelName and ChannelPriority. A session is created between each end of the channel. Sessions connect client and server "sinks." Sinks are responsible for relaying network messages along "sink chains" on both the client and the server. The client end of the channel resides at the end of the Client sink chain while the server end of the channel is at the front of the Server sink chain.

An Introduction to Remote Objects

The .NET Framework includes a rich library of tools for remote object sharing. The remoting framework allows objects to be marshaled by value or by reference to and from other applications on the network. In this section we will explore object marshaling, object activation, leases, and remote object configuration.

Marshal by Value

A client application can take a "brute force" approach to remoting by downloading an entire object—methods, properties and all—into its address space. The client can then interact with the object locally. The object's methods execute in the client's address space and can access resources intrinsic to the client's context, such as file systems and network connections. The process of copying an object or value from one address space into another is called "marshaling by value."

To accomplish this, the .NET Framework serializes the server object's entire class definition into a format that can be transmitted over the network, or channel. The serialized object can be represented in a binary format unique to the operating environment for optimal performance. Alternatively, the object can be serialized into an XML descriptor that can be reconstructed on any platform. Web Services employ the latter technique.

Almost any object can be marshaled by value. Any object that implements the ISerializable interface or implements its own serialization techniques can be transmitted by value (or copied) to another application.

Having a client download an entire object from a server is usually undesirable. It is an expensive operation that can require a lot of network bandwidth and client resources. Some classes have large method footprints that take time to serialize. In addition, transmitting an entire object over a network exposes that object's private instance variables to outsiders. Sometimes you do not have the option of downloading an entire object. In situations where the object needs to access resources unique to the server, such as a database or file system, you have no choice but to execute the object remotely. What should you do if you don't need the entire object but just need to call its methods remotely? Marshaling by reference might be the answer.

Marshal by Reference

The .NET Remoting framework supports marshal-by-reference objects as well as marshal-by-value ones. This means that a client application can download a reference to a remote object and interact with it as if it were locally defined. Although the object appears to "belong" to the client, its methods actually execute, via RPC, in the server's address space.

The .NET Remoting framework takes care of all the plumbing that makes remote object references work. Remote object references are actually stub classes generated by .NET. The stub class has the same method and property signature as the original class. The stub class, however, does not house any of the method implementations. Each call to a stub method begins a chain of events in which the method arguments are serialized and sent over the network to the remote object instance, where the actual method call is executed. The method's results are serialized, sent back to the client, unpackaged, and returned to the calling function as if the method had executed locally.

One of the exciting features about .NET Remoting is that this complexity can be completely masked from the developer. If everything is configured correctly a programmer can create a remote object stub simply by calling the New( ) (or comparable) constructor on a remote object. The .NET Remoting framework will generate a stub reference and return it in place of the actual object. Although the developer can step in and micromanage the remoting process if necessary, the .NET handles most common cases so well that the process is relatively painless.

Although the runtime Remoting Architecture makes it easy to create references to remote objects, you should use them judiciously. Your software incurs a performance hit each time you cross an application boundary. Make sure you keep this in mind when designing remote API for your application. For example, see if you can consolidate multiple calls to remote accessors into a single call to a more coarse-grained method.

Object Activation and State

Before you can begin instantiating and using remote objects, you need to let the .NET know about the remote object's activation and state. You should specify whether the client or the server will be responsible for the object's activation (creation, persistence, and destruction), be it server-activated or client-activated, and state. In the case of client-activated objects, you must also be wary of remote object leases.

Server-Activated Objects

Server-activated objects are objects whose life cycles are controlled exclusively by the server. Although clients can hold references to server-activated objects, the server only creates instances of these objects when one of their methods is called. Even though a client might create a stub object by calling New( ) or Activator.GetObject( ), the object does not yet need to be instantiated on the server. This "lazy loading" of server-activated objects conserves server resources and removes the need to make a network round-trip just to create the object's stub in the client. By default, server-activated objects only support default constructors (that is, constructors that don't take any arguments). Programmers must publish other constructors programmatically.

Server-activated objects come in two flavors: Singleton and SingletonCall. Singleton type objects never have more than one instance running on the server at a time. All requests to the server are serviced by that single instance. If no instance of the singleton object is in memory (for example, the first time one of its methods is called), the server creates a new instance and then services the request. The server is free to destroy a singleton object at any time as long as it creates a new one of the same type the next time it's requested. This means that the actual object the client stub points to might change over time. Because of this, singleton objects are, by definition, stateless. Singleton methods can declare local variables that will be unique for the duration of the request. However, any instance properties of the class will be shared across all clients or will be discarded if the object's instance is destroyed. Therefore, all client-specific states must be kept on the client side and passed to the singleton object with each method request.

SingletonCall type objects always have one instance on the server for each request. All requests to a SingletonCall type object will be serviced by a unique instance of the class. Subsequent requests are not guaranteed to be serviced by the same instance, however.

The following piece of code is an example of how a client can construct a server-activated object using the Activator.GetObject( ) method.

RemoteObjectClass MyRemoteClass = (RemoteObjectClass)Activator.GetObject(
typeof(RemoteObjectClass),
"tcp://computername:8080/RemoteObjectUri "
);

Now that the client has a reference to the MyRemoteClass object, it can begin invoking its methods directly.

Client-Activated Objects

As the name implies, client-activated objects have life cycles that are controlled by the client application. This paradigm is similar to the way local objects work. The server creates a unique instance of the object every time a client calls its constructor. This means that the same client application can hold references to several unique instances of a server-side object.

Client-activated objects maintain much more state than their server-activated counterparts because of the one-to-one mapping of client-side object stubs to server-side object instances. Client-activated objects behave the same way that locally instantiated objects do. Maintaining object state across a network is a difficult task. Fortunately, the .NET Framework takes complexity behind remote procedure calls and state maintenance out of the developer's list of details to worry about.

Client-activated objects have a greater potential impact on server resources than do server-activated objects. A client can create any number of instances of these objects. Stale object pointers can clutter the server with useless object instances, and distributed garbage collection is a difficult problem to solve. How does the server know when the client is finished using an object? Should it wait until the client releases its last reference to the object? Should the server try to communicate with the client periodically to see if it can reclaim the memory? These solutions are tricky to implement, particularly when network problems can arise at any moment to interfere with host-to-client communication. The .NET implements distributed garbage collection by using a lifetime lease strategy.

Remote Object Leases

When a client creates a new instance of a remote object, it must specify how long it intends to use it. Once the agreed lease duration has expired the server will contact the client and ask if it needs to renew the lease. If the client renews, everything is business as usual. If the client does not respond, the server waits a predetermined amount of time to see if the client wakes up. If the server does not hear from the client it assumes the object is no longer needed and reclaims the object's resources.

Leases implement the ILease interface that prescribes the properties and methods associated with leasing and lease renewal. Clients need not wait for the lease to expire before renewing; each call to a remote method can renew the lease. Clients can also call the Renew method synchronously to extend its life. A lease manager running on the server maintains a queue of clients that have requested leases. The manager monitors the queue and prompts clients to renew when their leases have expired.

The ILease interface is straightforward and contains only a few properties and methods. The leasing manager's queue contains a list of objects that implement this interface. Table 8-2 and Table 8-3 offer a more detailed definition of the leasing interface.

Table 8-2 ILease Public Instance Properties
Property | Description
CurrentLeaseTime | Gets the amount of time remaining in the lease.
CurrentState | Gets the current leaseState of the lease.
InitialLeaseTime | Gets or sets the initial time for the lease.
RenewalOnCallTime | Gets or sets the amount of time by which a call to the remote object increases the leaseTime.
SponsorshipTimeout | Gets or sets the amount of time to wait for a sponsor to return with a lease renewal time.
Table 8-3 ILease Public Instance Methods
Method | Description
Register | Overloaded. Registers a sponsor (client) for the lease.
Renew | Renews a lease for the specified time.
Unregister | Removes a sponsor from the sponsor list.

The leaseState property can take on one of the following states: Active (the lease is active and has not expired), Expired (the lease has expired and cannot be renewed), Initial (the lease has been created but is not yet active), Null (the lease is not initialized), and Renewing (the lease has expired and is looking for sponsorship).

The downside to leasing is that it is not as efficient as reference counting, the preferred mechanism for in-process garbage collection. Sometimes leases keep objects alive on the server even after the client has discarded the reference. Nonetheless, a well-managed leasing system is much cleaner to implement over error-prone networks and, over the long term, is almost as efficient as reference counting or pinging.

Configuration

Now that you understand the basic concepts behind client-activated objects, server-activated objects, and leasing, you are almost ready to write some code that uses .NET Remoting. One additional conceptual barrier stands in our way, however. How do clients discover remote objects and their properties? How is remote object metadata passed from server to client and what does that metadata describe?

First let's look at the metadata the client and server programs need to share objects. The following pieces of information are necessary to create and invoke remote methods.

  • The type of activation required. This specifies whether or not the object is client-activated or server-activated.
  • The complete metadata describing the object. The class's metadata includes a list of its public properties and types and a list of public method signatures.
  • The channel registered to handle requests for the object. This means the protocol and port number to which the remote object is bound.
  • The URI that uniquely identifies an object of that type. These last two pieces of metadata can be seen in the following example:
  • tcp://myserver.com:8080/remoteObjects/MyObject.
  • where tcp://myserver.com:8080 is the channel identifier and
    /remoteObjects/MyObject     is the URI.

Think back to the way Web Service clients work with the WSDL. The WSDL document contains all the preceding metadata. The activation method is implicitly server-activated. The class metadata is contained in the WSDL document. The channel is implicitly http, and the URI to the Web Service is also explicitly stated in the WSDL document. This information gives the .NET Remoting framework all it needs to generate the stub classes needed to make use of the Web Service.


Remoting Examples

In this section, we will drive home some object remoting concepts by creating some sample applications. We will begin by building a server-activated object and a client-activated object. Next we will explore the concept of distributed events by creating a Producer-Consumer application using the .NET Remoting platform.

Creating Remote Objects

Now let's get down to business and build a remotable object. For our example we will create two different remotable objects. The first will be a server-activated factorial object that computes large factorials on behalf of its clients. The second will be a remote shopping cart component that maintains a shopper's name and a list of items they intend to purchase. We will walk through the steps of designing, coding, and deploying each kind of object.

Factorial—A Server-Activated Remote Object

The factorial object will allow clients to offload the CPU-intensive task of computing factorials for large numbers to a server equipped to do the job. The object will have a single method, computeFactorial(int num), that takes a single integer and returns its factorial.

Now we'll follow the recommended steps to create this remote object.

Step 1. Design your Service

You need to answer these questions to design your remote service.

  1. What is your host application domain?
  2. What is your activation model?
  3. What channel and port will your service use?
  4. How will clients obtain your service's metadata?

The first question asks how we intend to develop our remotable object. We will use the C# programming language to implement a Windows application for our simple factorial object. Second, our activation model will be server-activated. Moreover, we will make our object a singleton so that clients can share a single instance of it on the server. Third, our application will use tcp port 8081. Finally our client will obtain the service's metadata by linking to a local DLL copy of the service assembly and will programmatically create service instances by calling the Activator.GetObject( ) method.

Step 2. Implement Your Host Application Domain

The code behind our factorial service will be easy to write. The service itself is a mere class that inherits from MarshalByRefObject. Listing 8-16 is a full listing of this class:

Listing 8-16 FactorialService.cs: The guts of our factorial service.

using System;
namespace Factorial
{
  public class FactorialService : MarshalByRefObject
  {
    public double computeFactorial(int num)
    {
      double result = 1;
      for (int i=2; i <= num; i++)
      {
        result *= i;
      }
      return result;
    }
  }
}

This class has no special details other than the fact that it inherits from MarshalByRefObject. This tells .NET that this object should be remoted by marshal-by-reference, instead of marshal-by-value, semantics.

The Listing 8-16 file is compiled into a DLL by executing the following command at the command prompt:

Csc /debug+ /t:library /out:Factorial.dll FactorialService.cs
Don't forget to use the VCVars32.bat script to initialize your command-line environment. This batch file can be found in the Program Files\Microsoft Visual Studio.NET\VC7\bin directory.

The Factorial DLL file will be linked into two other files: DirectHost.cs and FactorialClient.cs. DirectHost.cs will be used on the server side to publish the FactorialService class so that clients can use it. FactorialClient.cs will be the client-side program that looks up the object and invokes it.

Step 3. ConFigure the Remoting System

The remoting system can be configured in one of two ways. The program can import a static config file that contains the class's metadata or it can pass the metadata to the remoting system programmatically. Our example will use the second approach. Here are the lines of code from DirectHost.cs that do that:

WellKnownServiceTypeEntry entry = new WellKnownServiceTypeEntry
("Factorial.FactorialService","Factorial","MyFactorial"
,System.Runtime.Remoting.WellKnownObjectMode.Singleton);
RemotingConfiguration.RegisterWellKnownServiceType(entry);

The first line of code creates a WellKnownServiceTypeEntry object, which can be passed directly to the RegisterWellKnownServiceType( ) method that registers the service with the .NET Remoting system. The arguments to the WellKnownServiceTypeEntry are the class name, the assembly name (that is, the name of the DLL file that contains the class), the URI to which the service should be bound, and the mode in which this server-activated object will run. In this case the object is a singleton, which means that a single instance of the object will be shared across all clients.

Step 4. Create and Register the Channel

The channel defines the communications pipe that the server object will listen on while waiting for incoming requests. This includes the protocol and port number. Note that .NET Remoting Services are allowed to share port numbers. As long as their URIs are unique there will be no conflict. Registering the channel requires only a few more lines of code in DirectHost.cs.

System.Runtime.Remoting.Channels.Tcp.TcpServerChannel tcpch =
new System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(8081);
ChannelServices.RegisterChannel(tcpch);

The application must create an instance of a class that implements the IChannel interface. In this case we are using the TcpServerChannel class the .NET Framework provides. The HttpServerChannel class is also available for use. The TcpServerChannel class uses a binary formatter to serialize and deserialize all communication with the server. This provides better performance than the HttpServerChannel object but with less flexibility. In our previous example we passed the number 8081 into the constructor to indicate the port number that should be used to listen for incoming client requests.

Step 5. Publish Your Class

Once the channel is registered and the remoting system has been configured, our Factorial class is ready to be published for client use. Here is the Listing 8-17 for the DirectHost application:

Listing 8-17 DirectHost.cs: This program registers the FactorialService class with the .NET Remoting framework.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
public class DirectHost
{
  public static void Main()
  {
    initialize();
    Console.WriteLine("DirectHost is ready to process remote
messages.");
    String keyState = "";
    while (String.Compare(keyState,"0", true) != 0)
    {
      Console.WriteLine("Press a key and ENTER: G=GC.Collect, 0=Exit");
      keyState = Console.ReadLine();
      Console.WriteLine("Pressed: " + keyState);
      // Force a GC
      if (String.Compare(keyState,"G", true) == 0)
      {
        Console.WriteLine("GC Collect - start");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("GC Collect - done");
      }
    }
  }
  public static void initialize()
  {
    // register configuration
    System.Runtime.Remoting.Channels.Tcp.TcpServerChannel tcpch =
new System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(8081);
    ChannelServices.RegisterChannel(tcpch);
    WellKnownServiceTypeEntry entry = new WellKnownService
TypeEntry("Factorial.FactorialService","Factorial",
"MyFactorial",System.Runtime.Remoting.WellKnownObjectMode.
Singleton);
    RemotingConfiguration.RegisterWellKnownServiceType(entry);
  }
}

To compile this class, issue the following command from the command prompt:

  csc /debug+ /r:System.Runtime.Remoting.dll DirectHost.cs

All of the .NET Remoting function calls are contained in the initialize( ) function of this program. The program has a main( ) method so that it can be invoked from the command line. This method enters an input loop, waiting for the user to choose an option. The user can choose G to garbage collect the deployed object (which will be reinstantiated the next time a client invokes one of its methods) or 0 to exit. This input loop is necessary so that the program does not exit immediately, destroying the class we just deployed!

If everything is properly compiled your program should produce the output shown in Figure 8-2.

Figure 8-2 The result of running DirectHost.exe to publish the factorial service.

Step 6. Write Your Client

Assuming the DirectHost.exe program is running without a hitch, the FactorialService class is ready for mass consumption. Writing a remote service consumer is slightly easier than writing the producer. The consumer needs to register a TCP channel and then make a call to Activator.GetObject( ), passing in the type of the object it should return and the URI it should use to locate the object. This method will generate a stub class with the same signature as the remote class and will return it to the client for use in its address space. Upon receiving a reference to the remote object, the client is free to use it as it would any ordinary object.

Listing 8-18 is the full listing for our FactorialClient class:

Listing 8-18 FactorialClient.cs: The factorial service consumer.

using System;
namespace FactorialClient
{
  public class FactorialClient
  {
    public FactorialClient()
    {
    }
    public static void Main(String[] args)
    {
      System.Runtime.Remoting.Channels.ChannelServices.
RegisterChannel(new System.Runtime.Remoting.Channels.Tcp.
TcpChannel());
      try
      {
        Factorial.FactorialService obj =
(Factorial.FactorialService)Activator.GetObject(typeof(Factorial.
FactorialService), "tcp://localhost:8081/MyFactorial");
        System.Console.Out.WriteLine
(obj.computeFactorial(Int32.Parse(args[0])));
      }
      catch(Exception e)
      {
        System.Console.Out.WriteLine(e.StackTrace);
        System.Console.Out.WriteLine(e.Message);
      }
    }
  }
}

This class can be compiled by invoking the following command:

csc /debug+ /r:Factorial.dll /out:FactorialClient.exe FactorialClient.cs

The remote method call is extremely easy for the client to make. The computeFactorial( ) method can be invoked on the FactorialService reference without any RPC plumbing code; the .NET takes care of everything for us.

Shopping Cart—A Client-Activated Remote Object

Now we'll look at a more interesting example. Client-activated objects are more powerful than server-activated objects because they allow the remote object to maintain state across method calls. Because each reference to a client-activated object is guaranteed a unique, persistent instance on the server, clients can safely set object state without fear that it will be arbitrarily garbage collected. The object is safe as long as the client maintains its lifetime lease.

Our shopping cart program will maintain a few instance variables, the most important being an ArrayList of items to be purchased. This list can be augmented or trimmed by method calls from the client. Table 8-4 and Table 8-5 show a listing of the shopping cart's members.

Table 8-4 Shopping Cart Private Instance Properties
Property | Description
String firstName | The shopper's first name.
String lastName | The shopper's last name.
ArrayList items | A list of items to be purchased.
Table 8-5 Shopping Cart Public Instance Methods
Method | Description
void setName( ) | Sets the shopper's full name.
String getName(String firstName, String lastName) | Returns the shopper's full name.
void addItem(Object item) | Adds a new item to the shopping cart's contents.
void removeItem(int itemIndex) | Removes the item at the specified index from the shopping cart.
String getCartContents( ) | Returns a string describing the contents of the shopping cart.

Step 1. Design Your Service

Our shopping cart service will differ from the Factorial service in a few key areas. First the service will be client-activated, meaning that clients will control the object instances' life cycles. Second we will use the IIS to handle the deployment of the object. This will eliminate the need to write a DirectHost.cs program that houses the shared object. IIS will take care of registering the object in the .NET Remoting framework as long as we supply its metadata in a config file. Deploying the object through the IIS means that clients will be restricted to communicating with the object through only the HTTP channel. Just like before, our remote class will be written using the C# programming language.

Step 2. Implement Your Host Application Domain

All we need to do here is write our class and be sure to extend MarshalByRefObject. Listing 8-19 is the class listing.

Listing 8-19 ShoppingCart.cs: This object is client-activated and doesn't even know it!

using System;
using System.Runtime.Remoting;
using System.Collections;
namespace ShoppingCart
{
  // ShoppingCart is a client-activated object that
  // * Is exported in the remoting configuration file
  // * Has a Constructor with parameters
  // * Has public Properties that can set and get
  // * Has fields in which state is stored between calls
  // * Has methods that can be called to change the state
  // * Has overloaded methods
  public class ShoppingCart : MarshalByRefObject
  {
        public String firstName = "";
        public String lastName = "";
        private ArrayList cart = null;
    public ShoppingCart(String _firstName, String _lastName)
    {
            firstName = _firstName;
            lastName = _lastName;
    }
    // public properties
    public String FirstName
    {
      get
      {
        Console.WriteLine("First Name: {0}", firstName);
        return firstName;
      }
      set
      {
        lock(this)
        {
          firstName = value;
          Console.WriteLine("First Name: {0}", firstName);
        }
      }
    }
    public String LastName
    {
      get
      {
        Console.WriteLine("Last Name: {0}", LastName);
        return lastName;
      }
      set
      {
        lock(this)
        {
          lastName = value;
          Console.WriteLine("Last Name: {0}", LastName);
        }
      }
    }
    public void setName(String _firstName, String _lastName)
    {
       lock(this)
       {
         firstName = _firstName;
         lastName = _lastName;
       }
     }
      public void addItem(Object item)
      {
       lock(this)
       {
         if (cart == null)
           cart = new ArrayList();
         cart.Add(item);
       }
     }
     public void removeItem(int index)
     {
       if (cart != null && index < cart.Count)
       {
         lock(this)
         {
           cart.RemoveAt(index);
         }
       }
     }
     public String getCartContents()
     {
       String contents = firstName+" "+lastName+"'s shopping cart conents:\n";
       for (int i = 0; i < cart.Count; i++)
       {
         contents += cart[i]+"\n";
       }
       return contents;
     }
  }
}

This class can be compiled with the following command line:

csc /debug+ /t:library /out:ShoppingCart.dll ShoppingCart.cs

As you can see, nothing in this class definition indicates that the object will be client-activated (or even shared at all!). Unlike server-activated classes, client-activated classes don't have any restrictions on what they can do. They can maintain state, have specialized constructors, and expose public properties to be retrieved or updated.

Step 3. ConFigure the Remoting System

In this example we need to conFigure the IIS manually to deploy the object. Follow these steps to conFigure the IIS:

  1. Compile ShoppingCart.cs into a DLL file. Copy this file into the bin sub-directory under the ShoppingCart main directory.
  2. Start the Internet Services Manager and highlight the Default Web Site under the server node.
  3. Select Action \ New \ Virtual Directory on the menu and click Next.
  4. Enter ShoppingCart as the virtual directory's alias and click Next.
  5. Enter the full path up to but excluding the bin directory under the ShoppingCart main directory.
  6. Create a Web.config file (Listing 8-20) in the directory where the service was registered. This file includes the service's metadata and is automatically loaded by the IIS whenever a client attempts to access the object.

Listing 8-20 Web.config: This file contains the server-side metadata for our remote object. The file is loaded by the IIS.

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <activated type="ShoppingCart.ShoppingCart, ShoppingCart"/>
      </service>
    </application>
  </system.runtime.remoting>
</configuration>

Step 4. Create and Register the Channel

This step is handled by the IIS.

Step 5. Publish Your Class

This step is handled by the IIS.

Step 6. Write Your Client

We are able to write a much richer client than we were able to with the previous example. The ShoppingCart client will create separate ShoppingCart instances, each with different state values. The client will alter the objects' state by calling their methods. The client will lastly attempt to print out each object's state by calling the getCartContents( ) methods and sending the result to the console, which is shown in Listing 8-21.

Listing 8-21 Client.cs: This client program creates many instances of the client-activated shopping cart objects.

using System;
using System.IO;
using System.Text;
using System.Runtime.Remoting;
using System.Security.Policy;
using System.Threading;
public class Client
{
  public static int Main(string[] args)
  {
    // Load the Http Channel from the config file
    RemotingConfiguration.Configure("Client.exe.config");
    ShoppingCart.ShoppingCart cart1 = new ShoppingCart.
ShoppingCart("Bob", "Smith");
    cart1.setName("Bob","Jones");
    cart1.firstName = "Robert";
    cart1.addItem("Carrots");
    cart1.addItem("Peas");
    cart1.addItem("String Beans");
    cart1.addItem("Apples");
    ShoppingCart.ShoppingCart cart2 = new ShoppingCart.
ShoppingCart("Jane","Doe");
    cart2.lastName = "Dough";
    cart2.addItem("Porsche 911 Cabrio");
    cart2.addItem("Cartier sunglasses");
    cart2.addItem("Leather driving gloves");
    cart2.addItem("Map of Blue Ridge Parkway");
    ShoppingCart.ShoppingCart cart3 = new ShoppingCart.
ShoppingCart("Steve", "Jones");
    cart3.firstName = "Ron";
    cart3.lastName = "Jones";
    cart3.addItem("Microsoft Office XP");
    cart3.addItem("Microsoft .NET Developer Studio");
    cart3.addItem("Sun Solaris");
    cart3.removeItem(2);
    cart3.addItem("Microsoft Windows 2000 Server");
    System.Console.Out.WriteLine(cart1.getCartContents());
    System.Console.Out.WriteLine(cart2.getCartContents());
    System.Console.Out.WriteLine(cart3.getCartContents());
    return 0;
  }
}

To compile this program, copy the ShoppingCart DLL file into the client directory and type:

csc /debug+ /r:System.Runtime.Remoting.dll /r:ShoppingCart.dll
/out:client.exe client.cs

This client program only contains a single line of code, which is obviously related to .NET Remoting:

RemotingConfiguration.Configure("Client.exe.config");

This line loads a configuration file from the file system that contains the remoted object's metadata that the client needs to locate the class and create an instance of it. The contents of this file are displayed in Listing 8-22.

Listing 8-22 Client.exe.config: An XML configuration file describing the ShoppingCart client.

<configuration>
  <system.runtime.remoting>
    <application name="Client">
      <client url="HTTP://localhost/ShoppingCart">
        <activated type="ShoppingCart.ShoppingCart, ShoppingCart" />
      </client>
      <channels>
        <channel type="System.Runtime.Remoting.
Channels.Http.HttpChannel, System.Runtime.Remoting" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>
If everything compiles and deploys correctly, the client should
produce the following output:
Robert Jones's shopping cart contents:
Carrots
Peas
String Beans
Apples
Jane Dough's shopping cart contents:
Porsche 911 Cabrio
Cartier sunglasses
Leather driving gloves
Map of Blue Ridge Parkway
Ron Jones's shopping cart contents:
Microsoft Office XP
Microsoft .NET Developer Studio
Microsoft Windows 2000 Server

Remoting Events

In addition to distributed objects, the .NET Remoting framework supports distributed events. Distributed events are extremely useful for implementing the producer-consumer event paradigm in a networked environment. The producer-consumer design pattern dictates a loose coupling between the event producer and the event consumers. Consumers should be able to register and unregister themselves with no impact on the Producer. The producer does not even need to know how many (if any) consumers are interested in what it has to say.

This pattern can be used to solve many problems. One compelling use of the pattern is to avoid busy-waiting, or polling. An event consumer can register a callback function to be executed whenever a specific event occurs. The consumer can do something else, knowing that its event handler will be called at the appropriate time. The producer-consumer pattern is also frequently used in building UIs. However, most UI events involve producers and consumers in the same application domain.

In this section we will explore a distributed event architecture in which the producers and consumers can reside in different application domains or even different machines. The files used in this example can be found on the .NET Framework SDK. In the example we will pass a "greeting" event from producer to consumer to convey a simple message.

The Event Class

The Greeting event class will define the actual event object to be passed from producer to consumers. The event arguments object must be serializable so it can be passed by value into each of the registered consumers. This is accomplished by associating the SerializableAttribute with the event class. The GreetingEventArgs class will contain a single instance variable called greeting that will contain the salutation text.

Our class file will also contain a remotable service called "Waz" that listens for incoming greetings. The Waz service will be a server-activated Singleton class. Clients can call Zap's HelloMethod( ) method, passing in a greeting that they would like to announce. The Waz service will create an instance of the GreetingEvent class and will publish it to all the consumers. Listing 8-23 shows the zap.cs source file, which contains the event logic for the distributed events program.

Listing 8-23 Zap.cs: this file contains the event, eventArgs, and publishingService code for our distributed event system.

/*==================================================================
  File:    Zap.cs
This file is part of the Microsoft .NET Framework SDK Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.
This source code is intended only as a supplement to Microsoft
Development Tools and/or online documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
==================================================================*/
using System;
using System.Runtime.Remoting;
using System.Collections;
namespace Zap
{
  // Define the event arguments
  [Serializable]
  public class GreetingEventArgs : EventArgs
  {
    public GreetingEventArgs(string greeting)
    {
      this.greeting = greeting;
    }
    public string greeting;
  }
  // Define the event
  public delegate void GreetingEvent (object sender,
GreetingEventArgs e);
  // Define the Service
  public class Waz : MarshalByRefObject
  {
    // The client will subscribe and
    // unsubscribe to this event
    public event GreetingEvent Greeting;
    // Method called remotely by client
    public void HelloMethod(string greeting)
    {
      Console.WriteLine("Received String {0}", greeting);
      // Package String in GreetingEventArgs
      GreetingEventArgs e = new GreetingEventArgs(greeting);
      // Fire Event
      if (Greeting != null)
      {
        Console.WriteLine("Firing Event");
        Greeting(this, e);
      }
    }
  }
}

Notice the serializable attribute associated with the GreetingEventArgs class as well as the MarshalByRefObject superclass attached to the Waz service. This means that GreetingEventArgs will be passed by value into all the consumer application domains while Waz instances will be referenced remotely by clients.

The EventFireHost Service Application

We defined the Waz service class in the Zap.cs file. Now we need a console application to register the channel, register the service, and deploy the service through the remoting framework. The Host class in Listing 8-24, EventFireHost.cs, contains a Main( ) method that does this. The class performs these tasks programmatically instead of making use of a configuration file. Upon registering and deploying the service, the application sits and waits for a user to tell it to terminate.

Listing 8-24 EventFireHost.cs: The server-side service application.

/*==================================================================
  File:    EventFireHost.cs
This file is part of the Microsoft .NET Framework SDK Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.
This source code is intended only as a supplement to Microsoft
Development Tools and/or online documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
==================================================================*/
using System;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class Host
{
  public static void Main(string[] args)
  {
    // Manually load the http channel.
    // This could also be done in the remoting configuration file.
    ChannelServices.RegisterChannel(new HttpChannel(999));
    // Register the wellknown server type.
    // This could also done in the remoting configuration file.
    RemotingConfiguration.RegisterWellKnownServiceType(
                    Type.GetType("Zap.Waz, Zap"),
                    "EventFireHost/Waz.soap",
                    WellKnownObjectMode.Singleton);
    // We are done, wait until the user wants to exit
    Console.WriteLine("Host is ready to process remote messages.");
    Console.WriteLine("Press ENTER to exit");
    String keyState = Console.ReadLine();
  }
}

Once the EventFireHost service application is up and running, clients can begin subscribing to events, producing events, or both via the Waz object.

The Event Listener

We will now define a new class that creates a GreetingEventHandler callback method. This class can be included by any client application that wants to register itself as a GreetingEvent listener.

Our event handler class, Baz, will inherit from MarshalByRefObject. This will enable our client application to register a stub, or proxy, for this class to be called whenever the event fires. An instance of GreetingEventArgs will be passed by value into the instance of the Baz class residing in the client's application domain. The Baz event handler in Listing 8-25 will print the greeting to the client's console.

Listing 8-25 Wak.cs: This file contains the Baz GreetingEvent handler class.

/*==================================================================
  File:      Wak.cs--------------------------------------------------
This file is part of the Microsoft .NET Framework SDK Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.
This source code is intended only as a supplement to Microsoft
Development Tools and/or online documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
==================================================================*/
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using Zap;
namespace Wak
{
    // Marshal by Ref Object onto which the event will be fired
    public class Baz : MarshalByRefObject
    {
        [OneWay]
        public void GreetingHandler(object sender, GreetingEventArgs e)
        {
            Console.WriteLine("GreetingHandler callback : Greeting : {0}\n", e.greeting);
        }
    }
}

The OneWay attribute indicates that the calling program has no way to find out the results of the method call. It does not return any values, nor does it set any properties in the client stub that would indicate successful completion. As such, the .NET Remoting framework can choose to invoke this method asynchronously because the calling program does not need to wait for it to return.

Let's move on to the event handler client program that creates an instance of the Baz object. The Client class in Listing 8-26, EventSinkHost.cs, instantiates the GreetingHandler and registers it as a WellKnown Service. It might look strange that a client program is registering a remotable object as a singleton service. The EventHandler client is special in that it needs to act as a service so that the event dispatcher can call into it. When this happens, the event producer serializes the GreetingEventArgs object and passes it by value into the GreetingHandler( ) method.

Listing 8-26 EventSinkHost.cs: This client program registers the GreetingEventHandler as a WellKnownService.

/*==================================================================
  File:    EventSinkHost.cs
This file is part of the Microsoft .NET Framework SDK Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.
This source code is intended only as a supplement to Microsoft
Development Tools and/or online documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
==================================================================*/

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Zap;
using Wak;
public class Client
{
  public static void Main(String[] args)
  {
    // This could also be done with a Remoting configuration file
    // Register the HTTP Channel
    ChannelServices.RegisterChannel(new HttpChannel(888));
    // Register the wellknown server type.
    // This could also done in the remoting configuration file.
    RemotingConfiguration.RegisterWellKnownServiceType(
                    Type.GetType("Wak.Baz, Wak"),
                    "EventSinkHost/Baz.soap",
                    WellKnownObjectMode.Singleton);
    // We are done, wait until the user wants to exit
    Console.WriteLine("Host is ready to process remote messages.");
    Console.WriteLine("Press ENTER to exit");
    String keyState = Console.ReadLine();
  }
}

The URI given to the event handler service is EventSinkHost/Baz.soap. The event producer must know this URI so it can notify the program of the events when they occur. When executed, this program sits still, listening for incoming event notices.

Now we will move on to the event service clients and see what they do.

Event Clients

The first client class we will look at is relatively simple. It uses the .NET Remoting framework Activator to create a remote reference to the Waz Web Service class. Upon obtaining this reference the client invokes the remote HelloMethod( ) method five times. Listing 8-27 shows a client to connect to a service.

Listing 8-27 Client.cs: A simple client to connect to the EventFireHost service.

/*==================================================================
  File:    Client.cs
This file is part of the Microsoft .NET Framework SDK Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.
This source code is intended only as a supplement to Microsoft
Development Tools and/or online documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
==================================================================*/
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Zap;
public class Client
{
  public static void Main(String[] args)
  {
    // This could also be done with a Remoting configuration file
    // Register the HTTP Channel
    ChannelServices.RegisterChannel(new HttpChannel());
    // Obtain a Proxy to the SOAP URL
    Waz waz = (Waz)Activator.GetObject(
                      typeof(Waz),
                      "http://localhost:999/EventFireHost/Waz.soap"
                      );
    for (int i = 0; i < 5; i++)
    {
      // Occurs over SOAP to waz)
      waz.HelloMethod("Bill" + " " + i);
    }
  }
}

You can see the EventFireHost executable print out information about these methods as they occur. However, the EventSinkHost event listener executable remains silent. What happened?

Notice that the Waz service class only fires the event if its event property is not null.

     // Fire Event
     if (Greeting != null)
     {
         Console.WriteLine("Firing Event");
         Greeting(this, e);
     }

The service client is responsible for creating a remote reference to the GreetingEventHandler and set the event property. Therefore, event-producing clients need to create two remote references: one to call the Waz service and the other to obtain a reference to the event handler to be passed into the GreetingEvent constructor method. The modified client class file, Listing 8-28, follows.

Listing 8-28 EventRegistration.cs: This client calls the EventFireHost service and kicks off a distributed event to boot.

/*==================================================================
  File:    EventRegistration.cs----
This file is part of the Microsoft .NET Framework SDK Code Samples.

  Copyright (C) Microsoft Corporation.  All rights reserved.
This source code is intended only as a supplement to Microsoft
Development Tools and/or online documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
==================================================================*/
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Zap;
using Wak;
public class Client
{
  public static void Main(String[] args)
  {
    // This could also be done with a Remoting configuration file
    // Register the HTTP Channel
    ChannelServices.RegisterChannel(new HttpChannel(0));
    Baz baz = (Baz)Activator.GetObject(
                      typeof(Baz),
                      "http://localhost:888/EventSinkHost/Baz.soap"
                      );
    // Obtain a Proxy to the SOAP URL
    Waz waz = (Waz)Activator.GetObject(
                      typeof(Waz),
                      "http://localhost:999/EventFireHost/Waz.soap"
                      );
    // Subscribe to event : occurs over SOAP
    waz.Greeting += new GreetingEvent(baz.GreetingHandler);
    waz.HelloMethod("Hi from the client");
    // Unsubscribe to event : occurs over SOAP
    waz.Greeting -=new GreetingEvent(baz.GreetingHandler);
  }
}

Notice that this client makes an additional call to Activator.GetObject( ) to create a remote reference to the Baz GreetingEventHandler class. The reference to this handler is added to the event dispatcher in the line:

 waz.Greeting += new GreetingEvent(baz.GreetingHandler);

This time, through, the greeting property on the Waz object is no longer null. The Waz object reports that it is about to fire the event and notifies the listener by issuing the following statement.

  Greeting(this, e);

If everything is running properly you should see the EventSinkHost client print out the line: GreetingHandler callback: Greeting :Hi from the client. Remotable events are extremely powerful, if somewhat difficult to understand at first. We can extend our Event listener client to register itself on the service's event queue and remove itself once it becomes sick of receiving messages.


Remoting Object Model Overview

In this section we will explore the Remoting Object model in greater detail. We will look at some of the key concepts behind object remoting before jumping into the architecture itself. Hopefully this will provide more contextual information so you can gain a better perspective of the previous examples.

Remoting Object Model Concepts

.NET Remoting relies on some fundamental concepts surrounding what objects can and can't do over the network. Some objects can be serialized and passed-by-value back to the client, while others cannot. We will begin our exploration of the .NET Remoting Object model by defining the different levels of object remoting.

Agile Objects

Agile objects are accessed from any context and are always called directly. These types of objects can be invoked through direct references. Agile objects do not need to be called by proxy and have no constraints on what they can do. Most "regular" objects are considered agile.

Not Marshaled Type Category

Objects that do not derive from System.MarshalByRefObject or System.ContextBoundObject and do not have a [serializable] custom attribute are considered not marshaled. In other words, the object cannot be referenced remotely, nor can it be serialized into a form that can be stored or transmitted. This implies that an object of this type cannot be accessed from outside the application domain either by value or by reference.

Objects of this type are usually designed for use inside the application in which they are defined. By definition these types of objects are always accessed directly because all references to them are actual instances. Invoking methods on these objects do not suffer performance penalties because of argument and return result marshaling.

Marshal By Value Object Category

The only difference between these objects and those belonging to the Not Marshaled category is that MarshalByValue (MBV) objects can be serialized. This allows them to be copied directly into the address space of another application. In this way these objects are remotable, although no proxies are ever involved in their use. Because they must live within the application domain, all references to them must be actual instances. Therefore, their method performance is optimized.

MBV objects are easily identifiable by a serialization indicator in the class definition. Such classes usually take [serializable] attributes or else provide their own serialization methods by implementing the ISerializable interface. For example, the following class definition illustrates an MBV object.

[serializable]
public class MyMarshalByValueClass : ISerializable
{
  // ...
  private MyMarshalByValueClass(
                  SerializationInfo info,
                  StreamingContext context)
  {
    //. . .
  }

  // The ISerializable Implementation
  public virtual void GetObjectData(
                        SerializationInfo info,
                        StreamingContext context)
  {
    //. . .
  }
}

Marshal By Reference Object Category

MarshalByReference (MBR) objects make up the class of objects we just created in the Factorial and ShoppingCart examples. MBR objects all inherit, either directly or indirectly, from the MarshalByRefObject class.

All objects of this type have a distributed identity or some URI by which they can be located over a network. Instances of MarshalByRefObject classes must remain in the application domain where they are defined. Although references to these objects might exist in other application domains (or other machines), the actual instances must stay put.

MBR objects are particularly appropriate for circumstances in which objects that depend on a local resource need to be referenced remotely. Objects that access server-side resources, such as databases and file systems, and cannot be serialized are likely candidates for MBR remoting.

Whenever MBR objects are passed out of the Home application domain, either as a parameter to a method or a method's return result, the runtime takes care of marshaling a stub reference into the Target application domain. The runtime manages MBR reference semantics in all three of the following scenarios.

  • Whenever an MBR object is passed to another application domain in the same physical process.
  • Whenever an MBR object is passed between two different processes on the same machine.
  • Whenever an MBR object is passed between two different processes on different machines.

All out-of-process references to an MBR object must go through a layer of abstraction when performing operations on that object. This is so that the .NET Remoting framework can step in and insert a proxy class that handles marshaling the operation over a channel and back again.

All MBR object fields must be accessed through public accessors. The ShoppingCart example had two public properties, firstName and lastName, which appeared to be accessed directly by the ShoppingCart client. (For example, the statement cart2.lastName = "Dough"; appears to alter the lastName property directly.) This is, in fact, not true. What happened in this case was that the assignment operators for the firstName and lastName properties were overloaded to call the appropriate accessors in the ShoppingCart class. The accessors are implicitly associated with the properties via the following lines of code in ShoppingCart.cs

    public String FirstName
    {
      get
      {
        Console.WriteLine("First Name: {0}", firstName);
        return firstName;
      }
      set
      {
        lock(this)
        {
          firstName = value;
          Console.WriteLine("First Name: {0}", firstName);
        }
      }
    }
    public String LastName
    {
      get
      {
        Console.WriteLine("Last Name: {0}", LastName);
        return lastName;
      }
      set
      {
        lock(this)
        {
          lastName = value;
          Console.WriteLine("Last Name: {0}", LastName);
        }
      }
    }

Because all method calls on MBR objects must happen though indirection, MBR operations carry with them a performance penalty. The severity of this penalty depends on the degree of separation between the two objects, the amount of marshaling logic that is required, and the speed of the network link between them.

Context Bound Object Category

ContextBound objects are similar to MBR objects. The major difference is that ContextBound objects have a predefined context, or state of parameters, that must be defined for them to operate. All calling and passing semantics for ContextBound objects are the same as those for MBR objects.

ContextBound classes are marked by the ContextAttribute. This attribute defines the properties that a ContextBound object requires to operate correctly. These properties can define synchronization rules, transactions, just-in-time activation, security, or any other contexts that the object might be expecting. Context-specific data is stored into a shared memory mechanism managed by the .NET. The runtime allocates a multi-slot data store array to each process upon its creation. These slots house the contextual information unique to a thread or context.

ContextBound classes are opposite Agile classes in terms of contextual behavior. ContextBound classes are guaranteed to operate only in the context in which they were created. Conversely, Agile classes are guaranteed to operate in the context of the calling program.

Table 8-6 through Table 8-9 show the public members of the context objects.

Table 8-6 Public Static (Shared) Properties
Property | Description
DefaultContext | Gets the default context for the current application domain.
Table 8-7 Public Static (Shared) Methods
Method | Description
AllocateDataSlot | Allocates an un-named data slot.
AllocateNamedDataSlot | Allocates a named data slot.
FreeNamedDataSlot | Frees a named data slot.
GetData | Retrieves the value from the specified slot in the current context.
GetNamedDataSlot | Looks up a named data slot.
RegisterDynamicProperty | Registers a dynamic property implementing IContributeDynamicSink with the remoting service.
SetData | Sets the data in the specified slot on the current context.
UnregisterDynamicProperty | Unregisters a dynamic property implementing IcontributeDynamicSink.
Table 8-8 Public Instance Properties
Property | Description
ContextID | Gets the context ID for the current context.
ContextProperties | Gets the specified context property.
Table 8-9 Public Instance Methods
Method | Description
DoCallBack | Executes code in another context.
Equals | Overloaded. Determines whether two object instances are equal.
Freeze | Freezes the context.
GetHashCode | Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table.
GetProperty | Returns a specific context property by name.
GetType | Gets the type of the current instance.
SetProperty | Sets a specific property by name.
ToString | Returns a string representation of the context.

Remoting Object Model Architecture

The Remoting Object Model Architecture consists of interfaces, context-bound classes, application domain-bound classes, unbound classes, and value types. Let's look at how all this lays out. Figure 8-3 contains a high-level diagram of the inheritance hierarchy for all run-time classes.

Figure 8-3 A Logical class inheritance hierarchy.

Once a class is marked as MarshalByValue, AppDomainBound, or ContextBound, all subclasses of that class are similarly restricted. In other words, a class that inherits from a MarshalByValue class cannot also be marked as MarshalByReference.

Instance Fields and Accessors

The behavior of instance fields, or properties, varies across the class hierarchy. Depending on the type of object, instance properties can either be manipulated directly or through well-defined accessors. If the class is MarshalByValue, its fields can be directly accessed. No proxies are required because all the object's state resides in the local application domain.

Properties can also be directly manipulated if they belong to "this" or any of the object's parents. This is because the instance of the object in question must be operating in the application domain of its parent. If the object is MBV, its parent must also be MBV and, therefore, exists in the same application domain. Conversely, if the object is MBR, the actual instance of the object must run in the application domain where the object was born (even though references to the object might reside elsewhere). Here again the parent object must also reside in the same application domain so its properties can be accessed directly in memory.

Finally the runtime checks to see if the object is a proxy. If so, its fields are viewed and changed through accessors defined in the proxy class. Field values can safely be referred to via direct memory.

Statics

Static fields and methods are always agile. They are never remoted, and access to them is always done via direct memory. Furthermore, static fields are not subject to contextual rules placed on Context objects.

Passing Objects as Parameters

Agile objects can be passed and received to and from other application domains either by value or by reference. ContextBound objects, or objects that depend on the context in which they were born, are always passed by reference.

Overriding Methods on Object

The virtual methods defined on the class Object raise an interesting dilemma for the .NET Remoting framework. In the case of PassByReference remote objects, both the remote object and the local stub derive from the class Object. Therefore, these virtual functions are doubly defined. For instance, if ToString( ) is invoked on an object stub, should the .NET call the stub's ToString( ) method or the ToString( ) method associated with the remote object?

Generally speaking, these methods are only called remotely if the MBR object has explicitly overridden them. To be exact, here is what happens in the case of each of Object's virtual methods:

  • Equals( ) will execute remotely if overridden, locally if not.
  • GetHashCode( ) will execute remotely if overridden, locally if not.
  • ToString( ) will execute remotely if overridden, locally if not.
  • Equals( ) (static) always executes locally.
  • Memberwise Clone( ) always executes locally.


Conclusion

In this chapter we looked at how Microsoft's .NET Framework uses metadata to create exciting opportunities in distributed computing. Metadata is an indispensable tool for advertising object behaviors and properties. Although metadata has long existed in computing, recent advancements in Internet applications development have brought it to the forefront of our thinking.

We began by investigating the exciting new Web Services distributed computing paradigm. By combining descriptive metadata with a flexible and universal data packaging specification (XML) and a ubiquitous transport protocol (HTTP), we have created a platform in Web Services that enables object-sharing at a universal level. Tomorrow's distributed Web applications will not only make use of services hosted far and wide, but they will also be brought to market faster than ever before.

Web Services solve many of the problems that have long plagued widely distributed computing. Web Services use UDDI and WSDL to enable applications to discover and understand available services at runtime. SOAP provides a convenient platform and a language-neutral way to share objects among applications. By using HTTP as a transport protocol, Web Services can easily be deployed on existing network configurations. Finally broad support for Web Services in the .NET Developer Studio and other popular development platforms will enable developers to begin using the technology immediately.

In addition to supporting Web Services, Microsoft's Visual Studio .NET, the .NET Framework, and the Common Language Runtime environment arm developers with the tools they need to build advanced object-sharing applications. .NET applications can pass objects by value or by reference. Shared objects can be client-activated or server-activated. Regardless of the type of Remotable objects you build, the .NET Remoting Framework will take care of the complex task of serializing and transporting distributed data. Armed with these features, developers can solve virtually any distributed programming requirement.

We are entering an exciting time in computing. Hopefully, the small examples in this chapter have started you thinking of some practical applications for distributed technologies and Web Services in particular. Chapter 9 will lead you through real-world examples of how you can use .NET to create a fully functional enterprise application.


Building a Server Application

The best way to assimilate all that we have covered so far is to stop talking about it and start writing code. This is precisely the goal of Chapters 9 and 10. We will pull together a number of concepts in doing so, including XML content, XSLT skins, XML UI elements, SOAP, and XML Web services. Whether your goal is to understand the entire application or just specific pieces of it, we hope you get something useful from the code in these chapters. We will begin our programming endeavor by providing an overview of the application we will build. We will then focus on the server-side component of the application.

In this chapter we will design and implement the database logic, the data sets, and data adapters that provide access to the underlying datastore, the business logic that comprises the "guts" of the application, and, finally, a variety of Web Services that expose our business logic to clients.


Application Overview

In this chapter we build an example application—Margie's Travel Golf Reservation System—using the Microsoft .NET Framework. The Golf Reservation System is a tee-time registration application in which registered users can search for golf courses, view details on specific courses (tee descriptions, slope ratings, individual hole distances, pars, and handicaps), view available tee times at those courses, register for tee times, and view a list of previously registered tee times.

A production application of the the Golf Reservation System variety would normally provide much more functionality, but for the purposes of this book, the project allows us an under-the-hood view of .NET in action. It ties together course and user data, makes that data available through XML Web services, and displays it using the controller design pattern discussed in previous chapters.

Database Requirements

The database requirements for the Golf Reservation System's server-side component are relatively simple. We will use a Microsoft Access database to house all the course, golfer, tee information, and the tee times. Our goal is to keep the back-end of this system as simple as possible. The database will contain a small number of records for demonstration purposes, so a lightweight database management systems (DBMS) will be sufficient. If this application were meant to scale, we'd look into using a more sophisticated Relational Database Management System (RDBMS) such as the Microsoft SQL Server.

The XML Web services logic of the Golf Reservation System will not query the Access database directly. Instead, all data access will flow through a more abstract CourseDataSet class. The CourseDataSet will mimic the structure of the underlying DBMS and will provide quick and easy access to data from the application layer.

Using a DataSet to mask the underlying database has a number of advantages. First and foremost, it breaks the traditional ties that bind business logic to specific DBMS's. The Golf Reservation System's server-side component could be rewritten with a minimum amount of effort to support new database requirements. Second, direct in-memory DataSet access eliminates expensive boundary crossings to call into the database. Data can be retrieved quickly from the DataSet without the need for repetitive SQL queries. Finally reducing the load on the database engine frees it to operate more efficiently, enhancing the scalability of the application.

"Finder" XML Web services used for searching will create DataView objects to query the data contained in the CourseDataSet. Web Services can filter data by means of the DataView class's RowFilter property. Row filters use Expression objects to provide rich, SQL-like selection capability.

The server will implement a series of DataAdapter classes to handle all marshaling of data to and from the database. There will be a single DataAdapter class for each database table. These adapters will work together to load the table data into a consolidated data set. Data updates will require the following three separate steps:

  1. The business logic will update the CourseDataSet with the new record. This action will result in a new, removed, or updated data row whose state is reflected in the object's RowState property.
  2. The business logic will call the Update( ) method on the appropriate data adapter. The adapter will analyze the data set at this time and determine what action, if any, to perform to synchronize the underlying database tables.
  3. The business logic will call the AcceptChanges( ) method on the CourseDataSet object. This will return the dataset to a stable state and will reload any new information from the database.

Business Logic Requirements

The Golf Reservation System server business logic consists of all the object/database interactions that happen underneath the XML Web services facade. Database tables in the application are manipulated indirectly via a layer of high-level business objects. The class definitions for these objects are based directly on the underlying database schema.

Business Object C# Class Definitions

The application provides C# classes for each object represented by a database row as well as classes for commonly used containable business objects that have no single table relationship, such as an address. Each class contains traditional methods in getters and setters and in most cases multiple constructor methods.

Not every class in the application mirrors a database table as does the GolfCourse class. The same will likely hold true with any application you build inside the .NET Framework. For example, the fact that the application contains an address class as an address object can be useful because, at least within our application, it relates to and holds requirements for the same properties and methods across multiple business entities (both a golfer and a golf course have addresses). Obviously, one goal of a .NET application is code reuse, and although this is a simple example, it's a good example nonetheless.

Table 9-1 contains the business objects in the Golf Reservation System application.

Table 9-1 Business Entities in the Golf Reservation System Server Application
Class File | Description
Address.cs | The Address class defines a generic address structure. Instances of this class are referenced by the GolfCourse and Golfer objects.
GolfCourse.cs | The GolfCourse class contains general properties describing a golf course. All information regarding holes, yardages, pars, and so forth are delegated to helper classes.
Golfer.cs | The Golfer class defines a golfer in the system. Names, addresses, and security information are all found in this class.
Hole.cs | The Hole class defines a hole on a golf course. Hole objects contain properties such as par, distance, and handicap. Each set of tees (for example, red, white, blue) on the golf course has 18 unique hole instance.
Tee.cs | The Tee class defines a set of tees throughout the course. Properties in the class include slope, distance, a description, and an array of hole objects.
TeeTime.cs | The TeeTime class defines a unique tee time for a specific golf course on a certain date. Properties include the golf course ID, the golfer ID, and the date and time.

Web Services

The application contains only one XML Web service, conveniently named GolfCourseService. Numerous XML Web methods, however, are available within this lone service. The methods necessary for the TeeTimes application vary from search helpers to everyday "findById" methods.

GolfCourseService, like all XML Web services, is responsible for masking business object design complexities and particulars. The developer that needs to call into and retrieve data from Web methods need not know whether a table of addresses is in the database. The application calls into the GolfCourseService XML Web service via SOAP requests and receives SOAP responses. The .NET Framework, though, handles the complexities of creating, sending, receiving, and parsing the SOAP documents.

Browsing to an XML Web service with a Web browser results in a detailed description of the XML Web service, what methods it provides, and how to call into those methods using the supported protocols. Figure 9-1 shows the GolfCourseService called in this manner.

Figure 9-1 Browsing to an XML Web service provides detailed information about the methods provided.

Clicking a specific method (see Figure 9-2) offers a detailed description of that method and a form to invoke it, given your inputs. Note that submitting the form invokes the method and will perform the actions of that method as if it were called from an application If the method results in a new database insert, submitting this form will do the same. Also detailed on the method description page is the protocol syntax request and response for SOAP calls, HTTP POST, and HTTP GET.

Figure 9-2 Clicking a Web method offers a method description and protocol syntax for invoking the method.

As detailed in Figure 9-1, the GolfCourseService offers the following Web methods, listed here by method signatureP:

public GolfCourse[] FindAll()
public GolfCourse[] FindGeneric(String[] fields, String[] values)
public GolfCourse GetCourseDetail(String id)
public GolfCourse AddCourse(String name, String description,
String price, String streetAddress, String city, String state, String
postalCode, String country, String telephone)
public GolfCourse AddTee(int courseId, String description, float slope)
public GolfCourse AddHole(int teeId, int handicap, int length, int hole, int par)
public TeeTime AddTeeTime(int courseId, int golfer, DateTime date, DateTime time)
public TeeTime[] FindTeeTimesByGolfer(int golferId)
public Golfer AddGolfer(String firstName, String lastName,
String streetAddress, String city, String state, String postalCode,
String country, String email, String phone, String username, String password)
public Golfer FindGolferById(int golferId)
public Golfer ValidateLogin(String username, String password)
public TeeTime[] FindTeeTimesByDate(int courseId, DateTime date)
public TeeTime[] FindTeeTimesByDateRange(int courseId, DateTime
startDate, DateTime endDate)

These Web methods and the classes behind them make up the server-side component of our application and represent the proxy necessary for other applications to interact with and manipulate the Golf Reservation System business logic. We will break down these Web methods and the Golf Reservation System server business logic behind them in more detail later in the chapter.

User Requirements

A good division of labor between developer and designer is rarely achieved when rich UI are required for sophisticated applications. The more business logic an application developer can mask from the UI developer, the better. Combining XML Web services in the .NET Framework with the controller design pattern, though, results in a further separation of the duties of developers and designers. This is true not only because the controller XSLT skin is completely responsible for formatting controller result XML, but also because developer duties can be divided between server and client. The results are truly distributed applications in that one developer provides the XML Web services, perhaps in conjunction with a UI, while another developer, interested only in manipulating those XML Web services, builds an entirely separate client application.

Imagine the Golf Reservation System server application as a stand-alone service provided by a conglomeration of golf courses. Multiple Internet applications could call into those services and format and manipulate data as they saw fit: one client application might use the GolfCourseService only to retrieve data, while another might retrieve golfer information from the GolfCourseService, caring nothing about the course data also available.

Although the application is tailored to a specific client—one that allows validated users to register for tee times—the Web methods could be designed modeling only the data behind them. This approach would provide an XML Web service interface for retrieving the data published on the Internet that was not tailored to a specific client application design, but instead provided abstractions on a complex data store.


The Golf Reservation System Server Application

The Golf Reservation System server consists of the following: an SQL-compliant RDBMS, data adapter classes, .NET data sets, C# Object classes, and the GolfCourseService XML Web service and associated methods. When assembled, the C# Business Object classes, data set, data view, and data adapter classes, and the SQL RDBMS, will comprise the server application's business logic. The XML Web services layer will rest on top of this logic and provide a clean, easily accessible interface for client applications.

The Database

As mentioned earlier, the application will use a small Access database to store its information. The .mdb file can be transported easily and viewed on workstations as well as servers. The following sections represent a brief overview of the tables that appear in our database schema.

The Course Table

The course table will contain all the course-related information in the server application. Records can be inserted into the course table by issuing the following SQL statement:

INSERT INTO course(city, country, description, name, postalCode, price,
state, streetAddress, telephone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)

Table 9-2 contains the fields inside the course table.

Table 9-2 Course Fields
Column | Description
Id | Contains the course's ID. It is autoincremented by the database.
Name | Contains the course's name.
StreetAddress | Contains the course's street address.
City | Contains the course's city.
State | Contains the course's state.
PostalCode | Contains the course's postal code or ZIP code.
Country | Contains the course's country.
Telephone | Contains the course's telephone number.
Price | Contains the price for a round of golf.
Description | Contains a longer description of the golf course.

The Tee Table

The tee table will represent the different tees on the golf course (for example, red, white, and blue). Each tee will have a unique yardage and slope. Tee records can be created with the following SQL command:

INSERT INTO tee(courseId, description, distance, slope) VALUES (?, ?, ?, ?)

Table 9-3 contains the fields in the tee table.

Table 9-3 Tee Fields
Column | Description
Id | Contains the tee's ID. It is autoincremented by the database.
Slope | Contains a measurement of the difficulty of the tee.
Distance | Contains an aggregate measurement of all of the holes' distances.
Description | Contains a description of the tee (for example, red, white, and blue).
CourseId | Contains a primary key reference to the associated course.

The Hole Table

Records in the hole table represent a hole for a given tee. The hole's yardage, handicap, and par might vary with the tee. The following SQL command can be used to create new hole records:

INSERT INTO Hole(handicap, length, teeId, hole, par) VALUES (?, ?, ?, ?, ?)

Table 9-4 contains the fields in the hole table.

Table 9-4 Hole Fields
Column | Description
Id | Contains the hole's ID. It is autoincremented by the database.
TeeId | Contains a primary key reference to the associated tee.
Handicap | Contains the hole's handicap (1-18).
Length | Contains the hole's yardage.
Hole | Contains the hole number (1-18).
Par | Contains the hole's par.

The Golfer Table

The golfer table will contain records for each registered golfer in the system, regardless of whether or not they have booked a tee time. Golfer records can be created by issuing the following SQL command:

INSERT INTO golfers(city, country, email, firstName, lastName, phone, postalCode, state, streetAddress, [password], username) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Table 9-5 contains the fields in the golfer table.

Table 9-5 Golfer Fields
Column | Description
Id | Contains the golfer's ID. It is autoincremented by the database.
FirstName | Contains the golfer's first name.
LastName | Contains the golfer's last name.
StreetAddress | Contains the golfer's street address.
City | Contains the golfer's city.
State | Contains the golfer's state.
PostalCode | Contains the golfer's postal code or Zip Code.
Country | Contains the golfer's country.
Email | Contains the golfer's e-mail address.
Phone | Contains the golfer's telephone number.
Username | Contains the golfer's username, required to log in and request a tee time
Password | Contains the golfer's password, required to log in and request a tee time.

The Bookings Table

The bookings table will house all the requested course tee times. The table will contain only those tee times for a course that have been filled. Available tee times will be inferred by the "gaps" in this table. New bookings records can be created with the following SQL insert statement:

INSERT INTO bookings(courseId, golfer, teeDate, teeTime) VALUES (?, ?, ?, ?)

Table 9-6 contains the fields in the bookings table.

Table 9-6 Bookings Fields
Column | Description
Id | Contains the booking's ID. It is autoincremented by the database.
CourseId | Contains a primary key reference to the associated course.
Golfer | Contains a primary key reference to the associated golfer.
TeeDate | Contains the date of the tee time.
TeeTime | Contains the time of the tee time.

Data Sets and Data Adapters

Microsoft Visual Studio .NET aids your enterprise development efforts by providing data sets and data adapters to act as two layers of abstraction above the database. All data interaction to and from the business logic layer happens through data sets. Data sets act like large, multidimensional arrays for staging data that could come from databases, files, the Internet, or all of the above. They pull together the data that the application's business logic will need; and provide a clean, consolidated interface. Data sets are serializable, which means they can be saved or sent over the network to another running application.

Data adapters are the helper classes that populate the data sets and perform necessary database updates. They keep the data set classes simple by handling the dirty work of reading and writing data. Data adapter classes are tuned to a specific datasource such as a specific table in Access. They contain all the SQL Insert, Update, Select, and Delete statements needed to access that table. Multiple data adapters can be bound to a single data set. This is the case with the Golf Reservation System server application. Each data adapter is responsible for loading and storing a subset of the data set.

Data Adapters

The best way to create your data adapter classes is to use the .NET Studio Visual Designer. Use the Server Explorer pane to add a new data connection to your project. Upon adding this connection, the Server Explorer will update itself to show the connection and the data tables that can be managed by the connection.

Drag each of the tables you want to use onto the designer. The .NET Studio will generate a default data adapter for the table and display its properties in the Properties window. At this point you can conFigure the data adapter's Insert, Select, Update, and Delete SQL commands and other connection-related properties. We created data adapters this way for the course, hole, tee, golfer, and bookings tables in the application.

The .NET Studio will autogenerate the adapter code and place it into the .asmx.cs file of your project. You might need to tweak this code from time to time, so you should know what it does. (We needed to alter the SQL Insert command that some of the adapters were using to accommodate system-generated keys.) Let's take a look at how the GolferDataAdapter class works. The code fragments shown here come from the GolfCourseService.asmx.cs file.

First we need to create an instance of the data adapter class and its associated OLE-DB command classes.

this.oleDbUpdateCommand5 = new System.Data.OleDb.OleDbCommand();
this.oleDbSelectCommand5 = new System.Data.OleDb.OleDbCommand();
this.oleDbInsertCommand5 = new System.Data.OleDb.OleDbCommand();
this.oleDbDeleteCommand5 = new System.Data.OleDb.OleDbCommand();
this.golferDataAdapter = new System.Data.OleDb.OleDbDataAdapter();

Next we need to conFigure each of the OLE-DB command classes. This is done by specifying the SQL statement and filling in its required parameters. Again, the .NET Studio Visual Designer will create this code for you, although you may need to customize it in order for it to work with your database. The following is the configuration code for the Insert command. This code will create a new golfer record in the golfers table.

this.oleDbInsertCommand5.CommandText = "INSERT INTO golfers(city,
country, email, firstName, lastName, phone, postalCode," +
    " state, streetAddress, [password], username) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, " +
    "?, ?)";
this.oleDbInsertCommand5.Connection = this.oleDbConnection1;

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("city", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "city", System.Data.DataRowVersion.Current, null));
this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("country", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "country", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("email", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "email", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("firstName", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "firstName", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("lastName", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "lastName", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("phone", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "phone", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("postalCode", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "postalCode", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("state", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "state", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("streetAddress", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "streetAddress", System.Data.DataRowVersion.
Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("password", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "password", System.Data.DataRowVersion.Current, null));

this.oleDbInsertCommand5.Parameters.Add(new System.Data.OleDb.
OleDbParameter("username", System.Data.OleDb.OleDbType.Char, 50,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "username", System.Data.DataRowVersion.Current, null));

The Select, Update, and Delete commands for the golfers table (and all other tables in the application) work in much the same way. In all cases, you must specify a parameterized SQL CommandText string to execute against the database. You must also add a new object to the Parameters array for each runtime value you intend to use.

Next we bind the newly created OLE-DB command objects to the data adapter. This will force the adapter to use the SQL we just wrote when inserting, updating, deleting, or selecting data to and from the table.

this.golferDataAdapter.DeleteCommand = this.oleDbDeleteCommand5;
this.golferDataAdapter.InsertCommand = this.oleDbInsertCommand5;
this.golferDataAdapter.SelectCommand = this.oleDbSelectCommand5;
this.golferDataAdapter.UpdateCommand = this.oleDbUpdateCommand5;

Finally we need to specify the table mappings that map columns in the golfers table to columns in the associated data set. We will take a closer look at the data set momentarily.

this.golferDataAdapter.TableMappings.AddRange(new System.Data.
Common.DataTableMapping[] {
  new System.Data.Common.DataTableMapping("Table", "golfers", new
System.Data.Common.DataColumnMapping[] {
    new System.Data.Common.DataColumnMapping("city", "city"),
    new System.Data.Common.DataColumnMapping("country", "country"),
    new System.Data.Common.DataColumnMapping("email", "email"),
    new System.Data.Common.DataColumnMapping("firstName", "firstName"),
    new System.Data.Common.DataColumnMapping("id", "id"),
    new System.Data.Common.DataColumnMapping("lastName", "lastName"),
    new System.Data.Common.DataColumnMapping("phone", "phone"),
    new System.Data.Common.DataColumnMapping("postalCode",
"postalCode"),
    new System.Data.Common.DataColumnMapping("state", "state"),
    new System.Data.Common.DataColumnMapping("streetAddress", "streetAddress"),
    new System.Data.Common.DataColumnMapping("password", "password"),
    new System.Data.Common.DataColumnMapping("username", "username")
  })
});

That's it for the golfer data adapter! As you can see, creating the adapter objects for your application is a tedious and error-prone process. The .NET Studio will save a lot of time and debugging effort if you let it create these classes for you.

Data Sets

You will use data sets for practically all the database manipulation you need to do in your application. Data sets are in-memory databases that provide their own insert, update, delete, select, transaction, and filter semantics. By coding to the data set interface you make it much easier to port your application to another database later.

Typed vs. Untyped Data Sets

.NET data sets come in two flavors: typed and untyped. Typed data sets have built-in schemas associated with particular databases and columns. .NET uses this schema to extend the base DataSet class and create a new class that contains column references as typed properties and accessors.

Untyped data sets, on the other hand, do not contain extra information provided by the schema. You must manually extract and cast data values from the data set's array. The difference between typed and untyped data sets can be illustrated best by an example.

To retrieve a golfer's last name from an untyped data set you would have to execute the following lines of code:

string lastName = (string)courseDataSet1.Tables
["golfers"].Rows[0]["lastName"];

Compare that to using a typed version of the same data set:

string lastName = courseDataSet1.golfers[0].lastName;

The typed data set is much easier to use (especially when you have command completion turned on) and the .NET Studio will generate all the code for you.

Generating the Data Set

Once you've prepared your data adapter classes, you're ready to generate a typed data set from them. Right-click on the visual designer and select Generate Dataset. Alternatively, you can select the Generate Dataset option in the Tools menu. The Generate Dataset Wizard will walk you through the steps necessary to create a new DataSet class. The only detail you definitely need take care of is to select which data tables and data adapters the data set should use. After you have checked these, the .NET Studio will create a new typed data set schema and will use this schema to create the typed data set class definition.

The code behind the new data set is incredibly long (over two thousand lines for the server application), so we won't get into the guts here. Basically, the typed data set class provides simple marshaling utilites that either put information into the data set array or pull information out of it. Figure 9-3 shows how the designer looks with all the data adapters and data sets already configured.

Figure 9-3 A snapshot of the .NET Studio Designer showing the completed data acess objects.

Data Views

You will often need to filter the underlying database in your enterprise application. Whether you're showing information about one record or 20, you need some mechanism to extract and sort a well-defined subset of data. This was traditionally done via SQL SELECT statements that pull the filtered data view directly from the database. This approach shouldn't be used with data sets. Instead, .NET includes a DataView class to provide this functionality.

DataView objects are bound to a specific table in a data set. The table can be passed into the DataView object through its constructor or set manually via its Table public property. Once the data view is bound to a table, you can change its RowFilter and Sort properties at runtime to manipulate the data shown in the view. The Count property tells you how many records the view currently holds.

The RowFilter data view property uses the .NET Expression syntax to query the data set. .NET Expressions is a powerful query language with rich operator support. For example, the following expressions can be used to filter the course table:

  • "Name LIKE `Pinehurst*'"
  • "Price < 100.00"
  • "State = `North Carolina'"
  • "Price < 100.00 AND State = `North Carolina' AND (City = `Durham' OR City = `Chapel Hill' OR City =`Raleigh'"

DataView objects and their associated DataRowView objects are not typed, however. Therefore, you must use them as you would an untyped data set.

Business Objects

The Golf Reservation System server will contain class descriptions that model the database entities shown previously in an object format. Class definitions will exist for GolfCourse, Golfer, Tee, Hole, and TeeTime. A generic Address class will also be provided as a uniform means to represent golf course and golfer addresses. Instances or collections of instances of these objects will be returned by Web methods.

Golf Reservation System clients will need to download stub representations of these classes to make sense of the return results. Visual Studio .NET takes care of this by adding Web references. Other clients will need to query the WSDL document for the GolfCourseService XML Web service.

GolfCourse

The GolfCourse object provides an objectified view of objects in the course database table. The class definition for this object resides in GolfCourse.cs. The GolfCourse object is used primarily as a means to transport information back to an XML Web services client. The class uses public properties so that they can be included as a return result from an XML Web service call.

using System;
using System.Data;
namespace TeeTimes
{
  /// <summary>
  /// A GolfCourse
  /// </summary>
  public class GolfCourse
  {
    public int id;
    public String name;
    public String description;
    public String price;
    public String telephone;
    public Address address;
    public Tee[] tees;

The GolfCourse object has four different constructors. The first is the default constructor, which initializes all its properties to null.

    public GolfCourse()
    {
      id = 0;
      name = "";
      description = "";
      price = "";
      telephone = "";
      address = new Address();
      tees = null;
    }

The second constructor takes a row in a typed data set as an argument. The course's properties are initialized by the properties contained in the data row.

    public GolfCourse(courseDataSet.courseRow c)
    {
      name = "";
      description = "";
      price = "";
      address = new Address();
      tees = null;
      id = c.id;
      if (!c.IsnameNull())
        name = c.name;
      if (!c.IsdescriptionNull())
        description = c.description;
      if (!c.IspriceNull())
        price = c.price;
      if (!c.IstelephoneNull())
        telephone = c.telephone;
      if (!c.IsstreetAddressNull())
        address.street = c.streetAddress;
      if (!c.IscityNull())
        address.city = c.city;
      if (!c.IsstateNull())
        address.state = c.state;
      if (!c.IspostalCodeNull())
        address.postalCode = c.postalCode;
      if (!c.IscountryNull())
        address.country = c.country;
    }

The third constructor accepts a DataRowView object that was obtained via a DataView filter. The DataRowView is untyped, so the constructor must use array access to load its properties.

    public GolfCourse(DataRowView c)
    {
      id = 0;
      name = "";
      description = "";
      price = "";
      address = new Address();
      tees = null;
      id = Int32.Parse(c["id"].ToString());
      name = c["name"].ToString();
      description = c["description"].ToString();
      price = c["price"].ToString();
      telephone = c["telephone"].ToString();
      address.street = c["streetAddress"].ToString();
      address.city = c["city"].ToString();
      address.state = c["state"].ToString();
      address.postalCode = c["postalCode"].ToString();
      address.country = c["country"].ToString();
    }

The fourth and final GolfCourse constructor accepts a reference to a typed DataSet object and a primary key value. The constructor calls the DataSet object's FindById( ) method to obtain the correct row. If the row exists the constructor uses the data contained therein to initialize the GolfCourse object.

    public GolfCourse(courseDataSet ds, int courseId)
    {
      id = 0;
      name = "";
      description = "";
      price = "";
      address = new Address();
      courseDataSet.courseRow c = ds.course.FindByid(courseId);
      if (c != null)
      {
        id = courseId;
        name = "";
        description = "";
        price = "";
        address = new Address();
        if (!c.IsnameNull())
          name = c.name;
        if (!c.IsdescriptionNull())
          description = c.description;
        if (!c.IspriceNull())
          price = c.price;
        if (!c.IstelephoneNull())
          telephone = c.telephone;
        if (!c.IsstreetAddressNull())
          address.street = c.streetAddress;
        if (!c.IscityNull())
          address.city = c.city;
        if (!c.IsstateNull())
          address.state = c.state;
        if (!c.IspostalCodeNull())
          address.postalCode = c.postalCode;
        if (!c.IscountryNull())
          address.country = c.country;

The GolfCourse contructor locates all the course's Tee objects, instantiates them, and initializes the tees array property in the following:

        DataView teeView = new DataView(ds.tee);
        int teeCnt = ds.tee.Count;
        teeView.RowFilter = "courseId = "+courseId;
        int cnt = 0;
        tees = new Tee[teeView.Count];
        foreach (DataRowView r in teeView)
        {
          int tee_id = Int32.Parse(r["id"].ToString());
          Tee tee = new Tee(ds,tee_id);
          tees[cnt] = tee;
          cnt++;
        }
      }
    }

Public getters and setters are used to manipulate the GolfCourse's properties.

    public int getId() { return id; }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public Address getAddress() { return address; }
    public String getPrice() { return price; }
    public String getTelephone() { return telephone; }
    public void setId(int _id) { id = _id; }
    public void setName(String _name) { name = _name; }
    public void setDescription(String _description) { description = _description;}
    public void setPrice(String _price) { price = _price; }
    public void setTelephone(String _telephone) { telephone = _telephone; }

The class provides a setAddress( ) method to update the Address-dependent object.

    public void setAddress(String street, String city, String state,
String postalCode, String country)
    {
      address.setStreet(street);
      address.setCity(city);
      address.setState(state);
      address.setPostalCode(postalCode);
      address.setCountry(country);
    }
  }
}

Golfer

The Golfer object maintains all golfer and user states. Golfer object references can also be included within Web method result documents. The class definition for this object is shown in Golfer.cs.

using System;
namespace TeeTimes
{
  /// <summary>
  /// Summary description for Golfer.
  /// </summary>
  public class Golfer
  {
    public int id;
    public String firstName;
    public String lastName;
    public String email;
    public String phone;
    public String username;
    public String password;
    public Address address;

The Golfer object also has several constructors.

    public Golfer()
    {
      id = 0;
      firstName = "";
      lastName = "";
      email = "";
      phone = "";
      username = "";
      password = "";
      address = new Address();
    }

This constructor accepts a list of all Golfer properties to be initialized.

    public Golfer(String _firstName, String _lastName, String _email,
String _phone, String _streetAddress, String _city, String _state,
String _postalCode, String _country, String _username, String _password)
    {
      firstName = _firstName;
      lastName = _lastName;
      email = _email;
      phone = _phone;
      username = _username;
      password = _password;
      address = new Address();
      address.setStreet(_streetAddress);
      address.setCity(_city);
      address.setState(_state);
      address.setPostalCode(_postalCode);
      address.setCountry(_country);
    }

This constructor accepts the typed courseDataSet object and a Golfer id value. The constructor looks up the Golfer in the data set and sets its properties accordingly.

    public Golfer(courseDataSet ds, int golferId)
    {
      id = 0;
      firstName = "";
      lastName = "";
      email = "";
      phone = "";
      username = "";
      password = "";
      address = new Address();
      courseDataSet.golfersRow g = ds.golfers.FindByid(golferId);
      if (g != null)
      {
        id = golferId;
        if (!g.IsfirstNameNull())
          firstName = g.firstName;
        if (!g.IslastNameNull())
          lastName = g.lastName;
        if (!g.IsemailNull())
          email = g.email;
        if (!g.IsphoneNull())
          phone = g.phone;
        if (!g.IsusernameNull())
          username = g.username;
        if (!g.IspasswordNull())
          password = g.password;
        if (!g.IsstreetAddressNull())
          address.setStreet(g.streetAddress);
        if (!g.IscityNull())
          address.setCity(g.city);
        if (!g.IsstateNull())
          address.setState(g.state);
        if (!g.IspostalCodeNull())
          address.setPostalCode(g.postalCode);
        if (!g.IscountryNull())
          address.setCountry(g.country);
      }
    }

The following are the Golfer accessor and mutator methods:

    public int getId() { return id; }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public String getUserName() { return username; }
    public String getPassword() { return password; }
    public Address getAddress() { return address; }
    public void setId(int _id) { id = _id; }
    public void setFirstName(String _firstName) { firstName = _firstName; }
    public void setLastName(String _lastName) { lastName = _lastName; }
    public void setEmail(String _email) { email = _email; }
    public void setPhone(String _phone) { phone = _phone; }
    public void setUserName(String _username) { username = _username; }
    public void setPassword(String _password) { password = _password; }
    public void setAddress(String streetAddress, String city, String state,
String postalCode, String country)
    {
      address.setStreet(streetAddress);
      address.setCity(city);
      address.setState(state);
      address.setPostalCode(postalCode);
      address.setCountry(country);
    }
  }
}

Tee

The Tee class models the different types of tees that a golf course might have, such as red, white, and blue. Each Tee has its own set of yardage, slope, and distance properties as well as a collection of 18 unique Hole objects. Tee.cs contains the class definition.

using System;
using System.Data;
namespace TeeTimes
{
  /// <summary>
  /// Summary description for Tee.
  /// </summary>
  public class Tee
  {
    public int id;
    public double slope;
    public int distance;
    public String description;
    public int courseId;
    public Hole[] holes;

    public Tee()
    {
    }
    public Tee(int _id, int _courseId, String _description, float _slope)
    {
      id = _id;
      courseId = _courseId;
      description = _description;
      slope = _slope;
      holes = null;
    }

Again, we see the Data Set/ID constructor design pattern. You have the option to decide how "data-aware" your business objects should be. We overloaded our business objects' constructors to perform the most data-set access. Doing so simplifies the XML Web service logic.

    public Tee(courseDataSet ds, int teeId)
    {
      courseDataSet.teeRow t = ds.tee.FindByid(teeId);
      if (t != null)
      {
        id = teeId;
        courseId = t.courseId;
        description = t.description;
        slope = t.slope;

Here the Tee constructor builds an array of dependent Hole objects. We use a DataView object to filter the courseDataSet and generate a list of keys. We iterate over these keys, construct the objects, and create our holes array.

        DataView holeView = new DataView(ds.Hole);
        holeView.RowFilter = "teeId = "+teeId;
        holeView.Sort = "hole";
        int cnt = 0;
        holes = new Hole[holeView.Count];
        foreach (DataRowView r in holeView)
        {
          Hole hole = new Hole();
          hole.setTeeId(teeId);
          int hole_handicap = Int32.Parse(r["handicap"].ToString());
          int hole_length = Int32.Parse(r["length"].ToString());
          int hole_hole = Int32.Parse(r["hole"].ToString());
          int hole_par = Int32.Parse(r["par"].ToString());
          hole.setHandicap(hole_handicap);
          hole.setLength(hole_length);
          hole.setHole(hole_hole);
          hole.setPar(hole_par);
          holes[cnt] = hole;
          cnt++;
        }
      }
    }

These are the Tee accessors and mutators.

    public int getId() { return id; }
    public double getSlope() { return slope; }
    public int getDistance() { return distance; }
    public int getCourseId() { return courseId; }
    public String getDescription() { return description; }
    public Hole[] getHoles() { return holes; }
    public void setId(int _id) { id = _id; }
    public void setSlope(double _slope) { slope = _slope; }
    public void setDistance(int _distance) { distance = _distance; }
    public void setCourseId(int _courseId) { courseId = _courseId; }
    public void setDescription(String _description) { description = _description; }
    public void setHoles(Hole[] _holes) { holes = _holes; }
  }
}

Hole

Contrary to common sense, a single golf course might have 18, 36, 54, or more holes because each hole is different with respect to the tee from which the golfer is playing on a given day. A hole that plays 320 yards from the white tees might play 380 yards from the blue tees. To accommodate this requirement we decided to give each tee its own collection of 18 hole objects. The Hole class definition is found in Hole.cs.

using System;
namespace TeeTimes
{
  /// <summary>
  /// Summary description for Hole Class
  /// </summary>
  public class Hole
  {
    public int id;
    public int teeId;
    public int handicap;
    public int length;
    public int hole;
    public int par;
    public Hole()
    {
      id = 0;
      teeId = 0;
      handicap = 0;
      length = 0;
      hole = 0;
      par = 0;
    }

Hole constructors are always fed properties by value in the server project. This class might be extended later to provide the DataSet/ID constructor interface.

    public Hole(int _id, int _teeId, int _handicap, int _length, int _hole, int _par)
    {
      id = _id;
      teeId = _teeId;
      handicap = _handicap;
      length = _length;
      hole = _hole;
      par = _par;
    }
    public int getId() { return id; }
    public int getTeeId() { return teeId; }
    public int getHandicap() { return handicap; }
    public int getLength() { return length; }
    public int getHole() { return hole; }
    public int getPar() { return par; }
    public void setId(int _id) { id = _id; }
    public void setTeeId(int _teeId) { teeId = _teeId; }
    public void setHandicap(int _handicap) { handicap = _handicap; }
    public void setLength (int _length) { length = _length; }
    public void setHole (int _hole) { hole = _hole; }
    public void setPar (int _par) { par = _par; }
  }
}

TeeTime

The TeeTime class represents a tee-time booking on a specific date and time. TeeTime objects hold references to a golfer and a golf course. TeeTime objects can be obtained by XML Web services clients for a specific golf course to see what times are still available for play. TeeTime.cs shows the source code behind the TeeTime class.

using System;
namespace TeeTimes
{
  /// <summary>
  /// Summary description for TeeTime.
  /// </summary>
  public class TeeTime
  {
    public int id;
    public DateTime date;
    public DateTime time;
    public int courseId;
    public int golfer;
    public TeeTime()
    {
      id = 0;
      date = System.DateTime.Now;
      time = System.DateTime.Now;
      courseId = 0;
      golfer = 0;
    }
    public TeeTime(int _id, DateTime _date, DateTime _time, int _courseId, int _golfer)
    {
      id = _id;
      date = _date;
      time = _time;
      courseId = _courseId;
      golfer = _golfer;
    }

Again, we see the Data Set/ID constructor pattern.

    public TeeTime(courseDataSet ds, int ttId)
    {
      id = 0;
      date = System.DateTime.Now;
      time = System.DateTime.Now;
      courseId = 0;
      golfer = 0;
      courseDataSet.bookingsRow tt = ds.bookings.FindByid(ttId);
      if (tt != null)
      {
        id = ttId;
        if (!tt.IscourseIdNull())
          courseId = tt.courseId;
        if (!tt.IsgolferNull())
          golfer = tt.golfer;
        if (!tt.IsteeDateNull())
          date = DateTime.Parse(tt.teeDate);
        if (!tt.IsteeTimeNull())
          time = DateTime.Parse(tt.teeTime);
      }
    }
    public int getId() { return id; }
    public DateTime getDate() { return date; }
    public DateTime getTime() { return time; }
    public int getCourseId() { return courseId; }
    public int getGolfer() { return golfer; }
    public void setId(int _id) { id = _id; }
    public void setDate(DateTime _date) { date = _date; }
    public void setTime(DateTime _time) { time = _time; }
    public void setCourseId(int _courseId) { courseId = _courseId; }
    public void setGolfer(int _golfer) { golfer = _golfer; }
  }
}

Address

The Address object is used to provide a common address API for the server. GolfCourses and Golfers both hold references to objects of this type. This class could be extended later to support multiple address, driving directions, and other functionalities. The class definition can be found in Listing 9-1.

Listing 9-1 Address.cs: The Address utility class.

using System;
namespace TeeTimes
{
  /// <summary>
  /// Summary description for Address.
  /// </summary>
  public class Address
  {
    public String street;
    public String city;
    public String state;
    public String postalCode;
    public String country;
    public Address()
    {
      street = "";
      city = "";
      state = "";
      postalCode = "";
      country = "";
    }
    public String getStreet() { return street; }
    public String getCity() { return city; }
    public String getState() { return state; }
    public String getPostalCode() { return postalCode; }
    public String getCountry() { return country; }
    public void setStreet(String _street){ street = _street; }
    public void setCity(String _city){ city = _city; }
    public void setState(String _state){ state = _state; }
    public void setPostalCode(String __postalCode){ postalCode = _postalCode; }
    public void setCountry(String _country){ country = _country; }
  }
}

The GolfCourseService XML Web service

All Golf Reservation System Web methods will be assembled into a single XML Web service called GolfCourseService. The application publishes relatively few methods, so only a single XML Web service umbrella is required. All access to the server's business logic will pass through this API.

You will notice that most of the Web methods are small. .NET takes care of all the complex SOAP argument marshaling and our business object handles most of the data access and all of the nested object creation. The Web methods don't need to do too much at all.

FindAll( )

Syntax: GolfCourse[] FindAll()

FindAll( ) returns an array of GolfCourse objects. This method is called to get a complete listing of the courses maintained by the system. The method simply iterates over the entire course table in the GolfCourse data set.

[WebMethod]
public GolfCourse[] FindAll()
{
  int nCourses = courseDataSet1.course.Count;
  GolfCourse[] golfCourses = new GolfCourse[nCourses];
  int cnt = 0;
  foreach (courseDataSet.courseRow c in courseDataSet1.course)
  {
    GolfCourse gc = new GolfCourse(c);
    golfCourses[cnt] = gc;
    cnt++;
  }
  return golfCourses;
}

FindGeneric( )

Syntax: GolfCourse[] FindGeneric(String[] fields, String[] values)

FindGeneric( ) implements a simple yet flexible query interface into the course database. Clients can pass in arrays of property names and value expressions (including * wildcards) that can be used to generate a list of matching courses. The method makes use of the .NET DataView class to filter data in the courseDataSet1 data set.

An added bonus of this design is that this method will not need to change as fields are added to the course table and dataset.

[WebMethod]
public GolfCourse[] FindGeneric(String[] fields, String[] values)
{
  DataView cv = new DataView(courseDataSet1.course);
  cv.Sort = "name";
  String filterString = "";
  for (int x = 0; x < fields.Length; x++)
  {
    if (x >= 1)
    {
      filterString += " AND";
    }
    filterString += " "+fields[x]+" LIKE '"+values[x]+"'";
  }
  cv.RowFilter = filterString;
  int nCourses = cv.Count;
  GolfCourse[] golfCourses = new GolfCourse[nCourses];
  int cnt = 0;
  foreach (DataRowView c in cv)
  {
    int gcId = Int32.Parse(c["id"].ToString());
    GolfCourse gc = new GolfCourse(courseDataSet1, gcId);
    golfCourses[cnt] = gc;
    cnt++;
  }
  return golfCourses;
}

GetCourseDetail( )

Syntax: GolfCourse GetCourseDetail(String id)

GetCourseDetail( ) returns a complete representation of a golf course. Tees and Holes are returned as nested arrays. This method is a good example of a coarse-grained Web method. Recall that the GolfCourse constructor that is called here recursively builds arrays of Tees and Holes. A single call to this method retrieves enough information to be displayed on three different Web forms.

[WebMethod]
public GolfCourse GetCourseDetail(String id)
{
  int cid = Int32.Parse(id);
  GolfCourse gc = new GolfCourse(courseDataSet1,cid);
  return gc;
}

AddCourse( )

Syntax: GolfCourse AddCourse(String name, String description, String
price, String streetAddress, String city, String state, String
postalCode, String country, String telephone)

AddCourse( ) creates a new course entry in the golf course database.

[WebMethod]
public GolfCourse AddCourse(String name, String description, String price,
String streetAddress, String city, String state, String
postalCode, String country, String telephone)
{
  courseDataSet.courseRow newCourse = courseDataSet1.course.
AddcourseRow(city,country,description,name,
postalCode,price,state,streetAddress,telephone);
  courseDataAdapter.Update(courseDataSet1,"course");
  courseDataSet1.AcceptChanges();
  GolfCourse gc = new GolfCourse(newCourse);
  return gc;
}

AddTee( )

Syntax: GolfCourse AddTee(int courseId, String description, float slope

AddTee( ) creates a new tee entry for a particular golf course.

[WebMethod]
public GolfCourse AddTee(int courseId, String description, float slope)
{
  courseDataSet.teeRow newTee = courseDataSet1.tee.AddteeRow(courseId,description,0,slope);
  teeDataAdapter.Update(courseDataSet1,"tee");
  courseDataSet1.AcceptChanges();
  GolfCourse gc = new GolfCourse(courseDataSet1, courseId);
  return gc;
}

AddHole( )

Syntax: GolfCourse AddHole(int teeId, int handicap, int length, int hole, int par)

AddHole( ) adds a new hole record to the Hole database. The hole must be associated with a particular Tee object.

[WebMethod]
public GolfCourse AddHole(int teeId, int handicap, int length, int hole, int par)
{
  courseDataSet.HoleRow newHole = courseDataSet1.Hole.AddHoleRow
(handicap,length,teeId,hole,par);
  holeDataAdapter.Update(courseDataSet1,"Hole");
  courseDataSet1.AcceptChanges();
  courseDataSet.teeRow tr = courseDataSet1.tee.FindByid(teeId);
  int courseId = tr.courseId;
  GolfCourse gc = new GolfCourse(courseDataSet1, courseId);
  return gc;
}

AddTeeTime( )

Syntax: TeeTime AddTeeTime(int courseId, int golfer, DateTime date, DateTime time)

AddTeeTime( ) creates a new tee time record. The tee time record requires valid references to a golf course and a golfer record. The method also takes in two DateTime objects, one representing the date of the tee time and the other representing the time.

[WebMethod]
public TeeTime AddTeeTime(int courseId, int golfer, DateTime date, DateTime time)
{
  String dateString = date.ToShortDateString();
  courseDataSet.bookingsRow newBooking = courseDataSet1.
bookings.AddbookingsRow(courseId,golfer,dateString,time.ToString());
  bookingsDataAdapter.Update(courseDataSet1,"bookings");
  courseDataSet1.AcceptChanges();
  TeeTime tt = new TeeTime(0, date, time, courseId, golfer);
  return tt;
}

FindTeeTimesByGolfer( )

Syntax: TeeTime[] FindTeeTimesByGolfer(int golferId)

FindTeeTimesByGolfer( ) returns an array representing a complete tee time listing for a particular golfer. This listing can be used to remind a golfer of upcoming tee times. Again, we see the DataView object put to good use.

[WebMethod]
public TeeTime[] FindTeeTimesByGolfer(int golferId)
{
  DataView ttv = new DataView(courseDataSet1.bookings);
  ttv.Sort = "teeTime";
  ttv.RowFilter = "golfer = '"+golferId+"'";
  int nTimes = ttv.Count;
  TeeTime[] teeTimes = new TeeTime[nTimes];
  int cnt = 0;
  foreach (DataRowView c in ttv)
  {
    int ttId = Int32.Parse(c["id"].ToString());
    TeeTime tt = new TeeTime(courseDataSet1, ttId);
    teeTimes[cnt] = tt;
    cnt++;
  }
  return teeTimes;
}

AddGolfer( )

Syntax: Golfer AddGolfer(String firstName, String lastName, String
streetAddress, String city, String state, String postalCode, String
country, String email, String phone, String username, String password)

AddGolfer( ) creates a new golfer record in the database. This method requires all pertinent address and profile information.

[WebMethod]
public Golfer AddGolfer(String firstName, String lastName, String
streetAddress, String city, String state, String postalCode,
String country, String email, String phone, String username, String password)
{
  courseDataSet.golfersRow newGolfer = courseDataSet1.golfers.
AddgolfersRow(city,country,email,firstName,lastName,phone,
postalCode,state,streetAddress,password,username);
  golferDataAdapter.Update(courseDataSet1,"golfers");
  courseDataSet1.AcceptChanges();
  Golfer g = new Golfer(firstName,lastName,email,phone,
streetAddress,city,state,postalCode,country,username,password);
  return g;
}

FindGolferById( )

Syntax: Golfer FindGolferById(int golferId)

FindGolferById( ) looks up a specific golfer and returns a Golfer object with matching ID if one exists. It doesn't get any easier than this.

[WebMethod]
public Golfer FindGolferById(int golferId)
{
  return new Golfer(courseDataSet1,golferId);
}

ValidateLogin( )

Syntax: Golfer ValidateLogin(String username, String password)

ValidateLogin( ) is used to validate username/password combinations against the Golfer database. This is necessary to prevent fake tee times from being created. If the validation succeeds, the method returns the entire Golfer object. If it fails, the method returns null.

[WebMethod]
public Golfer ValidateLogin(String username, String password)
{
  DataView gv = new DataView(courseDataSet1.golfers);
  gv.RowFilter = "username = '"+username+"' AND password =
'"+password+"'";
  int gCount = gv.Count;
  Golfer g = null;
  if (gCount >= 1)
  {
    DataRowView gr = gv[0];
    int golferId = Int32.Parse(gr["id"].ToString());
    g = new Golfer(courseDataSet1,golferId);
  }
  return g;
}

FindTeeTimesByDate( )

Syntax: TeeTime[] FindTeeTimesByDate(int courseId, DateTime date)

FindTeeTimesByDate( ) returns an array of tee times for a specified course on a specified date.

[WebMethod]
public TeeTime[] FindTeeTimesByDate(int courseId, DateTime date)
{
  DataView ttv = new DataView(courseDataSet1.bookings);
  ttv.Sort = "teeTime";
  ttv.RowFilter = "teeDate = '"+date.ToShortDateString()+"' AND courseId = '"+courseId+"'";
  int nTimes = ttv.Count;
  TeeTime[] teeTimes = new TeeTime[nTimes];
  int cnt = 0;
  foreach (DataRowView c in ttv)
  {
    int ttId = Int32.Parse(c["id"].ToString());
    TeeTime tt = new TeeTime(courseDataSet1, ttId);
    teeTimes[cnt] = tt;
    cnt++;
  }
  return teeTimes;
}

FindTeeTimesByDateRange( )

Syntax: TeeTime[] FindTeeTimesByDateRange(int courseId, DateTime startDate, DateTime endDate)

FindTeeTimesByDateRange( ) returns an array of tee times for a specified course across a specified date range. Because of a strange problem in one of the earlier .NET Studio releases, we were unable to write Access records with fields of type DateTime. Thus, we were forced to serialize dates and times to strings before writing them out. This made the process of filtering by date range significantly more difficult.

[WebMethod]
public TeeTime[] FindTeeTimesByDateRange(int courseId, DateTime
startDate, DateTime endDate)
{
  DataView ttv = new DataView(courseDataSet1.bookings);
  ttv.Sort = "teeTime";
  String filterText = "courseId = '"+courseId+"' AND (";
  TimeSpan duration = endDate.Subtract(startDate);
  int nDays = duration.Days;
  for (int i = 0; i <= nDays; i++)
  {
    if (i > 0)
      filterText = filterText + " OR";
    DateTime tDate = startDate.AddDays(i);
    filterText = filterText+" teeDate = '"+
tDate.ToShortDateString()+"'";
  }
  filterText = filterText + ")";
  ttv.RowFilter = filterText;
  int nTimes = ttv.Count;
  TeeTime[] teeTimes = new TeeTime[nTimes];
  int cnt = 0;
  foreach (DataRowView c in ttv)
  {
    int ttId = Int32.Parse(c["id"].ToString());
    TeeTime tt = new TeeTime(courseDataSet1, ttId);
    teeTimes[cnt] = tt;
    cnt++;
  }
  return teeTimes;
}


Conclusion

In this chapter we've seen how to use XML and the .NET Developer Studio to create the server side of an enterprise application. We have used XML Web services, Web methods, data adapters, data classes, data views, and data connection utilities to make this happen. We are able to achieve a great deal of flexibility in our business logic by using XML Web services to erect an elegent partition around it. Our XML Web services can easily be called from a wide variety of clients, be it Web browsers, cell phones, thick client applications, or even other Web Services.

In Chapter 10 we will create a Web client that actually makes use of all this! Later, in Chapter 13, we will connect a wireless device to the same XML Web services.


Creating A Client

In this chapter we will build an example client application—Golf Reservation System Client—designed to connect to Golf Reservation System Server in Chapter 9, and we will do so using the Microsoft .NET Framework in conjunction with the controller design pattern. You will learn what the necessary components of building a .Net client application are as well as a common design pattern used for creating them: the controller. Understand that the controller is simply one of many design patterns available for creating .NET client applications, but it is the one we will use in this chapter.

We first discuss user requirements, how to understand and obtain information from XML Web services, the specifics of Golf Reservation System Client, the controller design pattern, and the procedure flow for our application. We then inspect relevant portions of Golf Reservation System Client to understand exactly what is happening where in the code. We conclude by reviewing the client application and brainstorming on how XML Web services and clients that are built to interact with XML Web services will evolve.


Client Application Overview

This application overview discusses the specifics of how our client application interacts with available Web Services. We discuss working with user requirements, obtaining information about XML Web services, and designing a client application—Golf Reservation System Client—based on the controller design pattern and the procedure flow it enforces.

The Golf Reservation System is a tee time registration application through which registered users can search for golf courses, view details on specific courses (tee descriptions, slope ratings, individual hole distances, pars and handicaps), view available tee times at those courses, make reservations, and view a list of previously registered times. Specifically, the Golf Reservation System client application discussed in this chapter is a Web client built to interact with the Golf Reservation System Server XML Web services.

A production application of the Golf Reservation System variety would provide much more functionality, but for the purposes of this book the Golf Reservation System allows us an under-the-hood view of .NET in action while it ties together course and user data, makes that data available through XML Web services, and displays it using the controller design pattern introduced in Chapter 5.

User Requirements

The ideal division of labor between developer and designer—in which the developer writes code and the designer creates interfaces—is rarely achieved when rich UIs are required for sophisticated applications. The more business logic an application developer can mask from the UI developer, the better. Combining the .NET Framework with the controller design pattern, though, results in further separating the duties of developers and designers, not only because the controller XSLT skin is completely responsible for formatting controller-result XML, but because developer duties can be divided between server and client. This separation creates truly distributed applications in that one developer or business provides the XML Web services, perhaps in conjunction with a UI, while another developer or business interested only in manipulating those XML Web services builds an entirely separate client application.

The handoff between server and client responsibilities occurs at the time of publishing of the XML Web services. As long as all requests are presented in the agreed-upon format, as defined by the XML Web services, the server application does not care what the client application does with the results. Likewise, as long as the server application accepts client requests in the agreed-upon format, the client application does not care how the server implements functionality.

Although the Golf Reservation System server application is in many ways tailored specifically to the Golf Reservation System Client we will build, it is entirely feasible that the Web methods could be designed to model only the data behind them and provide an XML Web service interface for retrieving the data. The result would be a service published on the Internet that is not tailored to a specific client application design, but instead provides abstractions on a complex data store. In this situation the clients would not necessarily be user-focused Web applications.

Imagine the Golf Reservation System server application as a stand-alone service provided by a conglomeration of golf courses. Multiple Internet applications could call into provided services and format and manipulate data as they saw fit. One client application might use the GolfCourseService to retrieve only golf course data (with or without displaying it), while another might retrieve golfer information from the GolfCourseService. A client could be developed by a partner business interested in collecting and manipulating the service result data, but not necessarily interested in displaying that data without further manipulation to a client—a server-to-server business interaction facilitated through XML Web Services.

Web Services

The Golf Reservation System contains only one Web Service, conveniently named GolfCourseService. Numerous other Web methods, however, are available within this lone service. The Web methods necessary for the Golf Reservation System vary from search helpers to everyday findById methods.

The GolfCourseService is responsible for masking business object design complexities and particulars. The developer calling and retrieving data from Web methods need not know whether a table of addresses is in the Golf Reservation System database. Instead, the .NET Framework allows back-end developers to create facades for complex business logic and publish the API for those facades (Web Services) to the network or Internet.

The Golf Reservation System client application calls into the GolfCourseService Web Service via SOAP requests and receives SOAP responses. The .NET Framework, though, handles the complexities of creating, sending, receiving, and parsing the SOAP documents. A developer seeking to manipulate a Web Service outside the scope of the .NET Framework would not only need an understanding of the business functionality available through the methods but an understanding of and an ability to generate SOAP calls to the methods, as well as an ability to parse the response SOAP documents.

Obtaining Information About a Service

Browsing to a Web Service results in the display of a description of what Web methods it provides and how to call into those methods using the supported protocols. Figure 10-1 is a screenshot of the GolfCourseService called in this manner, at http://localhost/TeeTimes/GolfCourseService.asmx.

Figure 10-1 Detailed information about the Web methods provided.

Clicking a specific method (shown in Figure 10-2) offers a detailed description of that method and a form to invoke it. Note that submitting the form on this page does invoke the method and will perform the actions of that method as if it were called from an application. If the method results in a new database insert, submitting this form will do the same. Also detailed on the method description page is the protocol syntax request and response for SOAP calls, HTTP POST, and HTTP GET.

Figure 10-2 A brief description and protocol syntax for invoking a Web method.

Understanding Web Service Method Signatures

The client application we will build requires little information to interact with the Golf Reservation System Server. We need only the method signatures of the Web methods provided by GolfCourseService and a brief description of method functionality if the intended method functionality is not immediately obvious by name.

These Web methods and the classes behind them make up the Golf Reservation System server application and represent the proxy necessary for other applications to interact with and manipulate the Golf Reservation System business logic. For more specific information about when and how to create Web Services from a server perspective, please see Chapter 9.

Golf Reservation System Client

Golf Reservation System Client consists of the following: a Web reference for connecting to the Golf Reservation System Server Web Services, C# Web forms (.aspx.cs classes and associated .aspx files), and all items necessary for implementing the controller design pattern.

To connect to the GolfCourseService Web Service provided by the Golf Reservation System server application, which can reside anywhere on the Internet or local network, you need to create a Web reference. A Web reference provides class and Web method definitions available from the services it contains and makes those business objects and methods available to the project. In Microsoft Visual Studio.NET, this is as simple as providing a URL to the Web Service, which in turn iterates over the resulting WSDL data and creates all the necessary stubs for use in your Visual Studio. NET project. Outside the application, any number of tools for querying WSDL documents published on the Internet or network can be used. Visual Studio .NET just makes it easy and virtually seamless for the developer.

In developing a C# client application, accessing the Web Services available within an existing Web reference requires creating Web forms. Adding a Web form to your application creates both a WebFormName.aspx.cs file and a WebFormName.aspx file. The .aspx page is an instance of its associated .aspx.cs class, referred to in many cases as the "Class-behind" file. Visual Studio .NET provides skeleton templates for creating C# Web forms, and adding a Web form to a project creates these templates for you, including all the required methods and formatting. We will use the template for the .aspx.cs file, but because we are employing the controller design pattern for our UI, we will not be using the skeleton template for the .aspx files.

Note that a Web form, as used by the client in a Web application, is simply an instance of that Web form's associated Class-behind file, the .aspx.cs file. Any public variables and methods defined in the Class-behind file will be available to any instances of the class, and any private methods and variables will be hidden from the instance.

To reference Web methods within the aspx.cs file, you must instantiate the Web Service to make it available to your class. You are then provided all available Web methods and object types defined in the Web Service. For example, the CourseDetail.aspx.cs file instantiates the GolfCourseService and calls the getCourseDetail method passing in the required courseID string. This example is as follows:

// localhost is the name of the computer hosting the Golf Reservation
//System Server
// First, create a protected reference to a GolfCourse object
// making it available to instances of this class, and assign
// the url variable `id' to a local variable, `_id'
protected localhost.GolfCourse gc;
string _id = Request.QueryString.Get("id");
// Next, instantiate the GolfCourseService
localhost.GolfCourseService gs = new localhost.GolfCourseService();
// Pass the local _id variable to the GetCourseDetail Web method and assign
// the result local GolfCourse gc.
gc = gs.GetCourseDetail(_id);

The result of the getCourseDetail method is a GolfCourse object as defined in the Golf Reservation System server application, and this object is made available for manipulation by the CourseDetail.aspx.cs class.

Any objects needed for reference in the .aspx file instance must be declared in the constructor of the .aspx.cs class, usually as protected. Therefore, in keeping with the CourseDetail.aspx.cs example, any instance (.aspx) of the CourseDetail.aspx.cs class will have a GolfCourse object named gc associated with the given id parameter available for manipulation. Any public or protected objects declared in the constructor are likewise available to each instance of the Web form.

Because we are using the controller design pattern, the resulting .aspx file is not a completed client page; instead it is an XML document. The document schema is agreed upon between the UI developer and the back-end developer. Therefore, after creating the Web form class and an associated .aspx XML file, the system developer duties are completed. The UI developer is now responsible for deciding how to format the data provided in the .aspx file, which can be used for any number of XSLT skins.

At this point the controller methodology takes over and formats the given-view XML (the .aspx result) with the appropriate template matches in the appropriate skin. The result, at least after using the ie5.xsl skin, is a page formatted to designer preference.

The Controller Design Pattern

The Golf Reservation System client application implements the controller design pattern for creating and managing the UI. Similar to the application of Web Services, the controller design pattern is platform- and application-non-specific in its implementation. Using XML and XSLT, all the UI designer requires is an agreed-upon schema for how the data is to be presented and working knowledge of how to use XSLT and associated formatting tools for presenting that data.

The controller implementation in the Golf Reservation System client application expects object-descriptive XML. That is, the result of a call to getCourseDetail.asmx.cs is an XML document describing the course in question, the syntax of which can be found in the code listing under getCourseDetail.aspx. Another popular implementation of the controller design pattern is to create a DTD abstracting both the data involved and the suggested (not actual) presentation of that data. For example, a list of courses from courseSearchResult.aspx could be formatted as

<courseList>
  <courseListItem>
    <name>
      Some Course Name
    </name>
    ...
  </courseListItem>
  <courseListItem>
    <name>
      Another Course Name
    </name>
    ...
  </courseListItem>
  . . .
  </courseList>

Optionally, courseSearchResult.asmx can format XML describing the contextual relationship of the data to a general layout, providing data in name/value pairs to the XSLT developer, such as

<list type="courseList">
    <listItem>
        <property>
            <name>Name</name>
            <value>Pebble Beach</value>
        </property>
        . . .
    </listItem>
    <listItem>
        <property>
            <name>Name</name>
            <value>Augusta National</value>
        </property>
        . . .
    </listItem>
</list>

Both models have advantages and disadvantages, and the application designer must decide which method best fits the application. At an initial view the object-descriptive model is the obvious choice, but keep in mind that it requires a new xsl:template block for nearly every type of request/response pair. However, the model describing the presentation lends itself to design consistency and an easier-to-maintain UI because it requires fewer templates.

The second approach involves contextual understanding of the data provided. Is a property significant? Which nodes can display on which Internet devices? These questions are significant in designing a client application, and a design team will probably come up with a hybrid of the two patterns instead of choosing one.

Procedure Flow

An application-perspective procedure flow from a Golf Reservation System Client page request is as follows:

  1. User requests page (controller.asp?view=courseDetail&id=12) either by typing an address in the browser location bar or by clicking a link somewhere. In either case the call for our sample client, is an HTTP GET method. (HTTP POST methods are also possible, though they're not used in this application.) All required variables are passed through the URL or as form variables. The Golf Reservation System client application assumes these variables are both present and formatted correctly. A production application would contain extensive validation.
  2. The controller locates the requested viewtype.aspx in the "content" folder and evaluates. Keeping with the CourseDetail example, the aspx file is located at /content/courseDetail.aspx.
  3. Calling the .aspx file forces the compiled .aspx.cs file to create an instance in the form of the aspx document. The Page_Load( ) function in the aspx.cs file is called and initializes the class. All public variables defined in the constructor are made available to the .aspx instance. The Web Service is instantiated and various Web methods are called. Database reads and writes are executed as necessary. The controller loads the XML result in a variable named xmlurl.
  4. The controller.asp retrieves the appropriate XSLT skin, ie5.xsl. (Golf Reservation System Client contains only one but can contain many skins.) The controller loads the XSLT in a variable named xslurl.
  5. The controller uses xslurl to transform xmlurl and returns the transformed result, in this case, an HTML document.


Client Code

This section contains the complete code listing for the Golf Reservation System client application. Every line of code is included here except for the autogenerated code created by Visual Studio.NET. Take special note of the code listing for CourseDetail.aspx.cs because this is the only listing that includes this autogenerated code, and it is necessary in creating Web forms. The .aspx files (our result XML) contains only one line of autogenerated code and is here in its complete form.

All files in Golf Reservation System Client follow this convention: the Class-behind file is named identical to the .aspx file with ".cs" appended. This format is not required, but it is standard and recommended. The templates, if you use Visual Studio .NET as an IDE, will default the names in this pattern.

As we begin to cover the code, note that each file is listed with its .aspx.cs file first, followed by its .aspx file. In a few cases no .aspx.cs file is necessary. Together, an .aspx.cs file and an .aspx file of the same name are called a "Web form" by the .Net Framework. Without the controller design pattern, each page viewed by the user is the server-side generated result of the Web form files.

The controller design pattern adds one more server-side step to the cycle. In this pattern the .aspx result returned is well-formed XML. The Web browser (or other Internet device) receives the result of this XML transformed by the XSLT skin. In the Golf Reservation System client application, ie5.xsl is responsible for transforming every page. A production application could have multiple skins, based on user preferences, target devices, and so forth.

Web Form Code

If you are familiar with ASP, you should also be familiar with most of the base syntax and method calls in this application. Reviewing the robust functionality of ASP is outside the scope of this book, but we will go into detail in the places where we use specific ASP functionality. Note that nearly all ASP functions are available, and in many cases extended in the .NET Framework development language, C#.

This first file we will look at is CourseDetail. CourseDetail is responsible for retrieving a GolfCourse object from the database given the id in the Request.QueryString object. The Request.QueryString object is a C# object that contains namevalue pairs present in the requested URL. Therefore, CourseDetail expects a URL similar to

http://localhost/TeeTimes/courseDetail.aspx?id=10001

Among the methods provided by the Request.QueryString object is the Get method in which you provide a value of type string which corresponds to the name in the namevalue pair expected in the URL. The Get method returns the value portion of the namevalue pair if the pair exists. Otherwise, the method returns a null response. Note that this method always returns a string because arguments present in the URL are always handled as strings, regardless of their form. The id in this example, although an integer, will be cast as a string until the program casts it otherwise. If you want to use a URL value as a datatype other than string, you must explicitly cast it as such.

Redundant code created by Visual Studio .NET has been removed, specifically all using declarations and the private methods Page_Init and InitializeComponent. CourseDetail.aspx.cs, contains these items, but the rest of the example code does not. While these portions are omitted here, the source files in their entirety can be found on the accompanying CD. These samples will not function without applying the generated code.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

The using statements are autogenerated by Visual Studio. NET and are required by the .NET Framework. They are present at the top of every .cs file in the Golf Reservation System client application.

namespace TeeTimesClient.content
{  public class courseDetail : System.Web.UI.Page
  {
     // provide a GolfCourse object to each instance
    protected localhost.GolfCourse gc;

In this example "localhost" is the name of the machine hosting the GolfCourseService Web Service. As long as a Web reference exists for the Web Service, this machine can be physically located anywhere on the Internet or local network.

This present scope represents all the variables (if defined other than private) that will be made available to instances of this class, specifically, CourseDetail.aspx. CourseDetail.aspx receives from this file a GolfCourse object named gc for manipulation. If the variable gc is not defined in this scope, the .aspx file will not have access to its properties and methods.

The public constructor method with the same name as its file is autogenerated by the IDE and is required by the .NET Framework. This method initializes an instance of an EventHandler to handle user events such as a mouse-click. The Golf Reservation System client application does not take advantage of the EventHandler, but the constructor is nonetheless required as part of the file.

    public courseDetail()
    {
      Page.Init += new System.EventHandler(Page_Init);
    }

The Page_Load method is also autogenerated by the IDE. This scope includes all the page load functionality you want to create. That is, all logic not called from client event handlers should be implemented in this scope.

    private void Page_Load(object sender, System.EventArgs e)
    {

CourseDetail .aspx expects a variable named id to exist in the Request.QueryString and to be formatted correctly. As stated before, Golf Reservation System Client contains virtually no error checking. A production implementation would, of course, contain rigorous validation and error checking.

// get the id from the url
string _id = Request.QueryString.Get("id");

This is our first example of how a variable is declared in C#, so let's take a closer look. The first portion of code casts the variable as a string. All variables in C# must have a type.

Following the cast, the name of the variable is declared, in this case _id. The variable name can be any alphanumeric string. These two portions alone are enough to declare a variable. Without the equal sign, though, no value is assigned to the variable and it is only a null reference. As you can't manipulate a null reference, you must assign a value to your variable before you call methods. Calling methods on a null reference results in a compile time error.

The far right side of the variable declaration can be an object or an expression that reults in an object, as long as the object type is the same as the variable to which it is being assigned. That is, you cannot assign an integer value to a variable declared as a string, and you cannot assign a string value to a variable declared as, say, a GolfCourse object. This type of error will also result in a compile time error.

The following line instantiates the GolfCourseService, which in this case exists on a networked computer named localhost. The localhost reference will need to be changed to the name of the computer where the Golf Reservation System server application lives. The local variable gs is now an instance of the GolfCourseService, and all methods in the GolfCourseService are now available in this scope of CourseDetail.aspx.cs.

                  // instantiate the Web service
      localhost.GolfCourseService gs = new localhost.GolfCourseService();

The variable gc now contains the golfCourse object returned from the call to the GetCourseDetail Web method.

    // assign gc to the appopriate GolfCourse object
gc = gs.GetCourseDetail(_id);
   }

The Page_Init function is autogenerated by Visual Studio .NET and should not be modified by the developer.

    private void Page_Init(object sender, EventArgs e)
    {
      //
      // CODEGEN: This call is required by the ASP.NET Web Form Designer.
      //
      InitializeComponent();
    }
    #region Web Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      this.Load += new System.EventHandler(this.Page_Load);
    }
    #endregion
  }
}

Like all .aspx files in Golf Reservation System Client, CourseDetail.aspx is responsible for returning well-formed XML to controller.asp. Notice the extensive XSLT in ie5.xsl used to create a rich UI for the functionality in this page. The result is a page providing general course information (see Figure 10-3) a detailed course description (see Figure 10-4), and a form to search for available tee times (see Figure 10-5).

The <%@ Page> declaration is a page definition required at the top of every .aspx file and is the only autogenerated code we leave there. Modifying this code out of its autogenerated format is unnecessary. Its purpose is to set page-specific parameters such as the language used, the Class-behind or .aspx.cs file, and the class from which the page inherits functionality. Again, this line is autogenerated by Visual Studio .NET and editing its content is unnecessary.

<%@ Page language="c#" Codebehind="courseDetail.aspx.cs" Auto
EventWireup="false" Inherits="TeeTimesClient.content.courseDetail" %>

Listing 10-1 is based on an agreed upon format between the XSL developer and the ASPX developer. The initial portion of the XML provides general page layout direction while the core of the document provides object-specific data—in this case, courseDetail information.

Notice that, until this point, the XML is layout-specific, that is, it describes the layout of the page instead of the object (or group of objects) to be formatted by the XSLT. This, again, is defined by an agreement between the XSLT (UI) and the ASPX (client application logic) developer. Because gc is defined as public scope in the aspx.cs class, this object is available for manipulation by the .aspx file. This is another area where the line between developer and UI designer is blurred.

Listing 10-1 CourseDetail.aspx: Provides an XML representation of the given course compliant with the XSLT model.

<document>
  <header>
    <title>Course Detail</title>
  </header>
  <section>
    <header>
      <title>Course Detail</title>
    </header>
 ...
  </section>
</document>
Figure 10-3 CourseDetail.aspx.cs displays GolfCourse object data given a URL variable named id.

Figure 10-4 CourseDetail.aspx.cs breaks the tee groups into individual holes, providing distance, par, and handicap for each. This is a good example of a functionally rich interface developed from object-based XML.

Figure 10-5 CourseDetail.aspx.cs also allows users to search for a tee time by date on this specific course.

The developer of the ASPX page must know what objects are available, what their names will be, and how those objects are defined. In most cases an ASPX page will receive proprietary data types or common data types such as arrays, and the ASPX developer must know how to handle these objects.

Also take note of the C# syntax used to display dynamic content within the .aspx file. ASP developers new to the .NET Framework and Web Services will be happy to know the syntax for such operations is the same as it is for an ASP application. To dynamically display a value, the syntax is

<%= objectReference.variableName %>

where variableName is a valid field name for the objectReference.

<!--- Display general golf Course Information --->
<courseDetail>
      <id>
        <%= gc.id %>
      </id>
      <name>
        <%= gc.name %>
      </name>
      <description>
        <%= gc.description %>
      </description>

CourseDetail.aspx is split into unique content sections, each defined in the XML as a courseDetailBlock. This particular courseDetailBlock displays the general course information: name, price, and, later, address. Distinguishing between different content sections through XML is necessary to provide the XSLT developer distinct relationships between different blocks of content.

      <courseDetailBlock description="Course Information">
        <courseItem type="courseInfo">
          <section>
            <header>
              <title>
                <%= gc.name %>: Course Information
  </title>
            </header>
          </section>
          <price>
            <%= gc.price %>
          </price>

The address object is included as part of the gc GolfCourse object, referred to by gc.address, and contains street, city, state, and postalCode variables. Obviously, the ASPX developer must have a solid understanding of the object types available.

       <!--- Display address object --->
       <address>
         <address1>
             <%= gc.address.street %>
         </address1>
            <city>
              <%= gc.address.city %>
            </city>
            <state>
              <%= gc.address.state %>
            </state>
            <zip>
              <%= gc.address.postalCode %>
            </zip>
          </address>
        </courseItem>
      </courseDetailBlock>
<!--- Display in another content section specific course information
including individual tee and hole descriptions --->
      <courseDetailBlock description="Course Description">
        <courseItem type="holeInfo">
          <section>
            <header>
              <title>
                <%= gc.name %>: Course Description
            </title>
            </header>
          </section>
          <tees>

The tees object returned with gc is an array of the tees associated with the given GolfCourse. A tee object also contains an array of holes, each having a par, distance, and number value. Notice in ie5.xsl that this becomes a grouped list with extremely rich UI functionality. The code here is used to loop over the array of tees and to access and display specific information about each one.

           <% for (int i=0; i < gc.tees.Length; i++) { %>

To include C# programming blocks, like the for loop, the syntax requires encapsulating the logic between <% and %>. This syntax will be familiar to developers with experience in ASP, but might look awkward to others.

            <tee>
              <slope>
                <%= gc.tees[i].slope %>
              </slope>
              <description>
                <%= gc.tees[i].description %>
              </description>
              <id>
                <%= gc.tees[i].id %>
              </id>
              <%
             // determine complete distance of a tee
             // through the sum of it's holes length values
                int teeDistance = 0;
                for (int j=0; j < gc.tees[i].holes.Length; j++) {
                  teeDistance = teeDistance + gc.tees[i].holes[j].length;
                }
              %>
              <distance>
                <%= teeDistance %>
              </distance>
             <!--- Display information about each hole object
in the tee --->
              <holes>
                <% for (int j=0; j < gc.tees[i].holes.Length; j++) {%>

Each Tee object contains an array of hole objects, as defined by the object schema for the Golf Reservation System Server. We iterate over the array of holes to display each one's significant variables. This loop results in numerous hole nodes, specifically the length of the hole array for this particular tee.

  <hole number="<%=gc.tees[i].holes[j].hole %>">
                  <par>
                    <%= gc.tees[i].holes[j].par %>
                  </par>
                  <handicap>
                    <%= gc.tees[i].holes[j].handicap %>
                  </handicap>
                  <distance>
                    <%= gc.tees[i].holes[j].length %>
                  </distance>
                </hole>
                <% } %>

Flow control and reading developer intent can be difficult to follow if you have never developed an ASP Web application, but it's not difficult to learn. Notice that we have to wrap our statement-ending } in <% and %>.

              </holes>
            </tee>
            <% } %>
          </tees>
        </courseItem>
      </courseDetailBlock>
       <!--- Display form for searching tee times --->
      <courseDetailBlock description="Schedule A Tee Time">

This courseDetailBlock is responsible for providing a form that allows the user to schedule a tee time (should any be available) for this specific course. All forms in the Golf Reservation System client application post to controller.asp, as defined by the controller design pattern. The view argument required by controller.asp is provided by a hidden variable later in the form. The XSLT developer depends on the ASPX developer to signify those inputs that are required by including the mandataory="yes" attribute. Notice also how the XML briefly returns to a layout-specific XML inside the <section> nodes.

      <form action="controller.asp">
<section>
            <header>
              <title>
                <%= gc.name %>.
                : Schedule a Tee Time </title>
            </header>
            <view>
              <properties>
                <property description="Course Name">
                  <text>
                    <%= gc.name %>
                  </text>
                </property>
                <property description="Month">

The <choice> node is an excellent example of how the ASPX developer can use the agreed-upon DTD to transfer the responsibility of formatting the display of data to the XSLT developer. Of course, no HTML choice node exists. Instead, a few nodes prompt the user for a choice with radio buttons, check box sets, and select boxes. Each of these nodes has distinct characteristics, both in functionality and in aesthetics. The end results, however, are more or less the same: the user is prompted to choose one or more selections from a list of possible options. Therefore, creating a <choice> node in the ASPX XML allows the XSLT developer to choose which HTML form input is most appropriate for the situation.

This instance of the <choice> node prompts the user to choose a month, day, and later a year for reserving a tee time.

                 <choice name="month" mandatory="yes">
                    <option value="01">January</option>
                    <option value="02">February</option>
                    <option value="03">March</option>
                    <option value="04">April</option>
                    <option value="05">May</option>
                    <option value="06">June</option>
                    <option value="07">July</option>
                    <option value="08">August</option>
                    <option value="09">September</option>
                    <option value="10">October</option>
                    <option value="11">November</option>
                    <option value="12">December</option>
                  </choice>
                </property>
                <property description="Day">
                  <choice name="day" mandatory="yes">
                    <% for(int i=1;i<=31;i++) { %>
                      <option value="<%=i%>"><%=i%></option>
                    <% } %>
                  </choice>
                </property>
                <property description="Year">
                  <%
System.DateTime theYears = System.DateTime.Now;
                     int thisYear = theYears.Year;
                     theYears = theYears.AddYears(1);
                     int nextYear = theYears.Year;
                  %>
                  <choice name="year" mandatory="yes">
                    <option value="<%=thisYear%>"><%=thisYear%></option>
                    <option value="<%=nextYear%>"><%=nextYear%></option>
                  </choice>
                </property>

The mandatory attribute is another example of utilizing the skills of the XSLT developer to implement intended functionality. Using the mandatory attribute informs the XSLT developer that a specific <input> node is required to submit the form. The XSLT developer can then generate whatever form validation code is necessary to ensure that the requirement is met. And given that each Internet device has its own implementation of validation, the responsibility should be left in the hands of the XSLT developer to determine the appropriate method.

The hidden fields in each form are submitted to the action page unbeknownst to the user. Hidden fields are not available for editing to the user, but instead are used as flags to the controller. This form provides a view hidden variable to tell the controller which view should be processed at form submission and also a submitted variable that lets the controller know that this request is a form submission. The courseID is also expected by the viewTeeTimes page, and is another example of the ASPX developer requiring knowledge of the application architecture and each individual file.

<property description="Username">
   <input type="text" name="username" mandatory="yes" />
                </property>
                <property description="Password">
   <input type="text" name="password" mandatory="yes" />
   <input type="hidden" name="submitted" value="true" />
                  <input type="hidden" name="view" value="viewTeeTimes" />
                  <input type="hidden" name="courseId" value="<%=gc.id%>"/>
                </property>
              </properties>
            </view>
          </section>
        </form>
      </courseDetailBlock>
    </courseDetail>
  </section>
</document>

Listing 10-2, CourseSearch.aspx is the search form that allows the user to find a course based on any combination of Name, City, State, Country, or Postal Code. CourseSearch has no Class-behind file as it's not a user-defined page. That is, CourseSearch.aspx requires no user information and displays the same for every user. This code only requires a form prompting the user for valid search fields. The form XML is in turn transformed by the skin—ie5.xsl—into a valid HTML form.

Listing 10-2 CourseSearch.aspx: Provides a form prompting the user for course search parameters.

<%@ Page %>
<document>
  <header>
    <title>Search For A Golf Course Near You</title>
  </header>
  <form action="controller.asp">
    <section>
      <header>
        <title>Course Search</title>
      </header>
      <view>
        <properties>
          <property description="Course Name">
            <input type="text" name="name" />
          </property>
          <property description="City">
            <input type="text" name="city" />
          </property>
          <property description="State">
            <input type="text" name="state" />
          </property>
          <property description="Country">
            <input type="text" name="country" />
          </property>
          <property description="Postal Code">
            <input type="text" name="postalCode" />
          </property>
          <property>
            <input type="hidden" name="view" value="CourseSearchResult" />
          </property>
        </properties>
      </view>
    </section>
  </form>
</document>

Listing 10-3, CourseSearchResult.aspx.cs, receives form data from CourseSearch.aspx and returns an array of GolfCourses satisfying the provided search parameters. The search parameters are provided by the user in the courseSearch.aspx form. The possible search parameters are Course Name, City, State, Country, and Postal Code.

Listing 10-3 CourseSearchResult.aspx.cs: Provides an array of Golf Course Objects satisfying given search parameters.

namespace TeeTimesClient
{
  public class CourseSearchResult : System.Web.UI.Page
  {
    protected localhost.GolfCourse[] results;
    private void Page_Load(object sender, System.EventArgs e)
    {
       // instantiate the GolfCourseService Web service
      localhost.GolfCourseService gs = new localhost.GolfCourseService();

The FindGeneric method expects an array of fields and an array of values, with the name and value variables occupying corresponding positions. Position X in the fields array is required to be a valid field for searching (such as name), while the same position in the values array needs to be its corresponding value (such as Wildwood Green). The FindGeneric method provides much stronger functionality than exact text searching and the ASPX developer needs to be aware of this functionality to take advantage of it.

This type of functionality, which is not immediately apparent given the name and method signature, is expected to be well-documented by the server-application developer. In this case FindGeneric accepts the string * as a wild card search parameter and will also accept part of a search string followed by *. The user can provide Wild* as a search parameter and the method will return, for example, a course with the name Wildwood Green. The same convention is applied to all search parameters in this method.

In this code we pull each of the expected arguments out of the Request.QueryString and insert their names into the fields array and their value into the values array. If the value in the Request.QueryString object is null, we place a * in the values array at the appropriate position, which represents a wild card and will satisfy all courses in the database. Thus, an empty search will result in a record set consisting of every course in the database.

// prepare the variables for calling FindGeneric
String[] fields = new String[5];
      String[] values = new String[5];
       // populate the fields and values arrays
      fields[0] = "name";
      string nameValue = Request.QueryString.Get("name");
      // if the value is null, set to `*'
if (nameValue == null || nameValue == "")
                  nameValue = "*";
      values[0] = nameValue;
      fields[1] = "city";
      string cityValue = Request.QueryString.Get("city");
      if (cityValue == null || cityValue == "")
        cityValue = "*";
      values[1] = cityValue;
fields[2] = "state";
      string stateValue = Request.QueryString.Get("state");
      if (stateValue == null || stateValue == "")
        stateValue = "*";
      values[2] = stateValue;
      fields[3] = "country";
      string countryValue = Request.QueryString.Get("country");
      if (countryValue == null || countryValue == "")
        countryValue = "*";
      values[3] = countryValue;
      fields[4] = "postalCode";
      string postalCodeValue = Request.QueryString.Get("postalCode");
      if (postalCodeValue == null || postalCodeValue == "")
           postalCodeValue = "*";
      values[4] = postalCodeValue;
      results = gs.FindGeneric(fields, values);
    }

Listing 10-4, CourseSearchResult.aspx, iterates over an array of GolfCourse objects and displays them. The array of GolfCourse objects, named results, is provided by the CourseSearchResult.aspx.cs file. The XSLT for this XML provides links to the CourseDetail page for each item in the array.

Listing 10-4 CourseSearchResult.aspx: A loop over the GolfCourse objects which satisfied search parameters and provide links to their appropriate CourseDetail pages.

<%@ Page language="c#" Codebehind="CourseSearchResult.aspx.cs"
AutoEventWireup="false" Inherits="TeeTimesClient.WebForm1" %>
<document>
  <header>
    <title>Course Search Results</title>
    <description>
    </description>
  </header>
  <section>
    <header>
      <title>Search Results</title>
    </header>
    <courseList>
       // for each Golf Course, provide id and general information
      <% for(int i=0; i < results.Length; i++) { %>
      <courseListItem>
        <name>
          <%= results[i].name %>
        </name>
        <id>
          <%= results[i].id %>
        </id>
        <city>
          <%= results[i].address.city %>
        </city>
        <state>
          <%= results[i].address.state %>
        </state>
      </courseListItem>
      <% } %>
    </courseList>
  </section>
</document>

Figure 10-6 shows the results of Listing 10-4.

Figure 10-6 The GolfCourse objects that satisfied the search parameters.

Listing 10-5, login.aspx, like CourseSearch.aspx, contains no Class-behind file and is only responsible for prompting input from the user. This file contains two forms, one for a registered user in the database to log into the Golf Reservation System client application, and one for a new golfer to register with Golf Reservation System by providing a username, password, and general user information. This file is only responsible for creating two valid forms prompting the user for input, either a username/password combination or enough data to create a new user record.

Listing 10-5 login.aspx: Provides a form for a user to sign in if they are a registered user or to register if they are not.

<%@ Page language="c#" Codebehind="login.aspx.cs"
AutoEventWireup="false" Inherits="TeeTimesClient.content.
myTeeTimes" %>
<document>
  <header>
    <title>My Tee Times</title>
  </header>
  <form action="controller.asp">
    <section>
      <header>
        <title>Please Log In</title>
      </header>
      <view>
        <description>
          Log in to to view your saved Tee Times
        </description>
        <properties>
          <property description="Username">
            <input type="text" name="username" mandatory="yes" />
          </property>
          <property description="Password">
            <input type="text" name="password" mandatory="yes" />
          </property>
          <property>
            <input type="hidden" name="submitted" value="true" />
<input type="hidden" name="view" value="viewMyTeeTimes" />

Notice that this page contains two distinct forms, each with different hidden view input types. The first form posts to viewMyTeeTimes, while the second form posts to register. We know this by inspecting the hidden input view arguments present in each form.

          </property>
        </properties>
      </view>
    </section>
  </form>
  <form action="controller.asp">
    <section>
      <header>
        <title>Register</title>
      </header>
      <view>
        <description>
If you do not yet have a username and password, please provide the
information below to become a registered user.
        </description>
        <properties>
          <property description="Choose a username">
            <input type="text" name="username" mandatory="yes" />
          </property>
          <property description="Choose a Password">
            <input type="text" name="password" mandatory="yes" />
          </property>
          <property description="First Name">
            <input type="text" name="firstName" mandatory="yes" />
          </property>
          <property description="Last Name">
            <input type="text" name="lastName" mandatory="yes" />
          </property>
          <property description="Address">
            <input type="text" name="streetAddress" mandatory="yes" />
          </property>
          <property description="City">
            <input type="text" name="city" mandatory="yes" />
          </property>
          <property description="State">
            <input type="text" name="state" mandatory="yes" />
          </property>
          <property description="Postal Code">
            <input type="text" name="postalCode" mandatory="yes" />
          </property>
          <property description="Country">
            <input type="text" name="country" mandatory="yes" />
          </property>
          <property description="email">
            <input type="text" name="email" mandatory="yes" />
          </property>
          <property description="Telephone">
            <input type="text" name="phone" mandatory="yes" />
<input type="hidden" name="submitted" value="true" />
          </property>
          <property>
            <input type="hidden" name="view" value="register" />
          </property>
        </properties>
      </view>
    </section>
  </form>
</document>

The input name attributes are significant in each form in Golf Reservation System Client. These name attributes (that is, name="phone", name="email") must be identical to the variable names expected by the Class-behind file of the .aspx to which each form is posted. This implementation of Golf Reservation System Client assumes all form variables will be present and formatted correctly. This, of course, is not a real-world example. Register.aspx.cs (Listing 10-6) receives information from login.aspx if the user submitted the new user form. This information is used to create a new user in the database using the addGolfer Web method.

Listing 10-6 register.aspx.cs: Creates a new user record based on user input from login.aspx.

namespace TeeTimesClient.content
{
  public class register : System.Web.UI.Page
  {
    protected localhost.Golfer newGolfer;
    private void Page_Load(object sender, System.EventArgs e)
    {
      localhost.GolfCourseService gs = new localhost.GolfCourseService();
      // Get all values from the Request.QueryString object
string usernameValue = Request.QueryString.Get("username");
      string passwordValue = Request.QueryString.Get("password");
      string firstNameValue = Request.QueryString.Get("firstName");
      string lastNameValue = Request.QueryString.Get("lastName");
      string streetAddressValue = Request.QueryString.Get
("streetAddress");
      string cityValue = Request.QueryString.Get("city");
      string stateValue = Request.QueryString.Get("state");
      string postalCodeValue = Request.QueryString.Get("postalCode");
      string countryValue = Request.QueryString.Get("country");
      string emailValue = Request.QueryString.Get("email");
      string phoneValue = Request.QueryString.Get("phone");
      // Add a new golfer to the database
      newGolfer = gs.AddGolfer(firstNameValue,lastNameValue,
streetAddressValue,cityValue,stateValue,postalCodeValue,
countryValue,emailValue,phoneValue,usernameValue,passwordValue);
    }

Listing 10-7, register.aspx, simply displays a thank-you message to the user after a registration is submitted. Notice the generic XML—no specific object XML is in this file.

Listing 10-7 register.aspx: Displays a thank-you message to the user following the registration routine in register.aspx.cs.

<%@ Page language="c#" Codebehind="register.aspx.cs"
AutoEventWireup="false" Inherits="TeeTimesClient.content.register" %>
<document>
  <header>
    <title>My Tee Times</title>
  </header>
  <section>
    <header>
      <title>Thank You for Registering</title>
    </header>
    <view>
      <description>
        Thanks for registering
        <%= newGolfer.firstName%>
- Now that you are a registered user, you can set up Tee Times with
any of our partner Golf Courses.
      </description>
    </view>
  </section>
</document>

The scheduleTeeTime file receives a courseId, a golferId, and a System.DateTime object from the Request.QueryString object, builds a registration, and inserts it into the database using the AddTeeTime Web method. It also provides the Golfer, GolfCourse, and TeeTime object to its .aspx file.

Notice the lack of error checking. If the golferIdValue is not present in the Request.QueryString object, or if it's not a valid golferId, Golf Reservation System Client will throw a run-time error. The variable golfer will be a null reference and scheduleTeeTime will function incorrectly.

scheduleTeeTime.aspx.cs - Build a registration and insert it into the database
namespace TeeTimesClient.content
{
  public class scheduleTeeTime : System.Web.UI.Page
  {
    protected localhost.TeeTime tt;
    protected localhost.Golfer golfer;
    protected localhost.GolfCourse gc;
    private void Page_Load(object sender, System.EventArgs e)
    {
      localhost.GolfCourseService gs = new localhost.GolfCourseService();
      int courseIdValue = Int32.Parse(Request.
QueryString.Get("courseId"));
      int golferIdValue = Int32.Parse(Request.QueryString.Get
("golferId"));
      System.DateTime dateValue = System.DateTime.Parse
(Request.QueryString.Get("teeTime"));
      // AddTeeTime requires two System.DateTime objects
       //  The first is parsed for the date, the second for the time
tt = gs.AddTeeTime(courseIdValue,golferIdValue,dateValue,dateValue);
      golfer = gs.FindGolferById(golferIdValue);
      gc = gs.GetCourseDetail(Request.QueryString.Get("courseid"));
    }

Listing 10-8, the scheduleTeeTime.aspx file, displays a notification that the selected tee time was inserted into the database. The object-based XML model used in this page affords the XSLT developer design luxury in creating a UI for displaying the tee time. Though ie5.xsl doesn't do much with the scheduleTeeTime.aspx result, a VoiceXML skin could read off the tee time detail, or the skin could hook into a calender client and build a calender entry.

Listing 10-8 scheduleTeeTime.aspx: Displays a thank-you message after a tee time is registered.

<%@ Page language="c#" Codebehind="scheduleTeeTime.aspx.cs"
AutoEventWireup="false" Inherits="TeeTimesClient.content.
scheduleTeeTime" %>
<document>
  <header>
    <title>My Tee Times</title>
  </header>
  <section>
    <header>
      <title>Tee Time Scheduled</title>
    </header>
    <description>
Thank you for registering a tee time. The detail of your registration is below.
    </description>
    <teeTimeDetail>
      <golfer>
        <name>
          <%= golfer.firstName %>
          <%= golfer.lastName %>
        </name>
      </golfer>
      <course>
        <id>
          <%= gc.id %>
        </id>
        <name>
          <%= gc.name %>
        </name>
      </course>
      <date>

Once an .aspx page has a handle on an object and the developer knows the variable datatype, the complete library of C# methods is available. Here, the System.DateTime object returned with the tee time is parsed into a readable format for the XSLT using the ToShortDateString( ) and ToShortTimeString( ) methods.

        <%= tt.date.ToShortDateString() %>
      </date>
      <time>
        <%= tt.time.ToShortTimeString() %>
      </time>
    </teeTimeDetail>
  </section>
</document>

The viewMyTeeTimes file receives a valid golferID and returns TeeTime objects associated with that user, as well as the associated GolfCourse objects. As an example of error checking, if the golferID is not valid, this class returns a protected Boolean redirect informing the instance .aspx file that the user is invalid and needs to be redirected.

Notice also that GolfCourseService is provided to the .aspx file because it's defined as a protected variable. Choosing where you should perform the Web method calls is an application design decision. The Class-behind files should perform as much business logic as is possible, but in many cases you might choose to implement only getters and setters inside the .aspx.cs and use the .aspx result document instead to perform Web method calls. This is an especially attractive model when implementing the controller design pattern because the .aspx file isn't the final tier of the application. This does, however, place the burden of exception handling on the ASPX developer. Doing so in the Class-behind file might be more logical.

Listing 10-9, viewMyTeeTimes.aspx, is responsible for validating that a username and password are valid and returning an array of tee times for the validated user by calling the FindTeeTimesByGolfer Web method.

Listing 10-9 viewMyTeeTimes.aspx.cs: Validates a user and returns an array of TeeTime objects for that user.

namespace TeeTimesClient.content
{
  public class viewMyTeeTimes : System.Web.UI.Page
  {
    protected localhost.TeeTime[] tt;
    protected localhost.Golfer golfer;
    protected localhost.GolfCourse gc;
    protected bool redirect = false;
     // Provide the GolfCourseService to instances
    protected localhost.GolfCourseService gs = new localhost.
GolfCourseService();
    private void Page_Load(object sender, System.EventArgs e)
    {
      string usernameValue = Request.QueryString.Get("username");
      string passwordValue = Request.QueryString.Get("password");
      golfer = gs.ValidateLogin(usernameValue,passwordValue);
      // if ValidateLogin returns a null reference, set
       // boolean redirect to true
if (golfer == null) {
        redirect = true;
      } else {
        int golferIdValue = golfer.id;
        tt = gs.FindTeeTimesByGolfer(golferIdValue);
      }
    }

Listing 10-10, the viewMyTeeTimes.aspx file, iterates over an array of TeeTime objects for a specific user, displaying the course name (with a link to the courseDetail page for that specific course), date, and time. If the Boolean variable redirect, provided by the ViewTeeTimes.aspx.cs Class-behind file, is true, the array of teeTimes and the golfer values will be null, so the username and password provided by the user failed the validateLogin method in ViewTeeTimes.aspx.cs.

Listing 10-10 viewMyTeeTimes.aspx.cs: Provides a list of registered tee times for a validated user.

<%@ Page language="c#" Codebehind="viewMyTeeTimes.aspx.cs"
AutoEventWireup="false" Inherits="TeeTimesClient.content.
viewMyTeeTimes" %>
<document>
  <header>
    <title>My Tee Times</title>
  </header>
  <section>
    <header>
      <title>My Tee Times</title>
    </header>
    <view>
      <description>
        <%
          if (redirect)
            Response.Write("Your username and password failed
validation. If you feel this is an error, please try logging in
again. If you are not yet a registered user, please click 'My Tee
Times' above and complete our registration Form.");
          else
            Response.Write("Your registered Tee Times Are Listed Below");
        %>
      </description>
      <% if (!redirect) {%>
      <myTeeTimes>
        <% for (int i=0; i<tt.Length;i++) { %>
        <myTeeTime>
          <course>
            <courseId>
              <%= tt[i].courseId%>
            </courseId>

Because it is defined in the proper scope as protected, complete Golf-CourseService functionality is provided here in the .aspx page, including the ability to communicate with Web Services. Here we call the toString method on the courseId value in the TeeTime array so we will have a string value to pass into the GetCourseDetail method. We then assign the returned value to our local variable gc.

            <% gc = gs.GetCourseDetail(tt[i].courseId.ToString()); %>
            <name>
              <%= gc.name %>
            </name>
          </course>
          <teeTime>
            <date>
              <%= tt[i].date.ToShortDateString() %>
            </date>
            <time>
              <%= tt[i].time.ToShortTimeString() %>
            </time>
          </teeTime>
        </myTeeTime>
        <% } %>
      </myTeeTimes>
      <% } %>
    </view>
  </section>
</document>

Figure 10-7 shows the result of Listing 10-10.

Figure 10-7 ViewMyTeeTimes displays all tee times associated with a specific user and provides links to courseDetail.aspx for the associated golf courses.

Listing 10-11, the viewTeeTimes file, receives an optional username and password combination, a courseId representing a golf course in the database, and year, month, and day values. It returns an array of available tee times for the selected year, month, and day, and a link to immediately register for those tee times if they are not already registered and if the username and password resolves to a valid user.

Listing 10-12, the viewTeeTimes.aspx file, iterates over an array of TeeTimes for a given course and, if a valid user is returned from the Class-behind file, it displays a link for registering those tee times. If a valid user is not present, a <noRegister> node is appended to the document. If a tee time in the array is already registered for, a <taken > node is appended, but the tee time information is still provided.

Listing 10-11 viewTeeTimes.aspx.cs: Retrieves valid TeeTimes given a courseID. It also returns a valid user if the username and password validate.

namespace TeeTimesClient.content
{
  public class viewTeeTimes : System.Web.UI.Page
  {
    protected localhost.TeeTime[] tt;
    protected localhost.Golfer golfer;
    protected localhost.GolfCourse gc;
    protected int selectedYear;
    protected int selectedMonth;
    protected int selectedDay;
    protected bool redirect = false;
    private void Page_Load(object sender, System.EventArgs e)
    {
      localhost.GolfCourseService gs = new localhost.GolfCourseService();
      string usernameValue = Request.QueryString.Get("username");
      string passwordValue = Request.QueryString.Get("password");
      string courseIdString = Request.QueryString.Get("courseId");
      golfer = gs.ValidateLogin(usernameValue,passwordValue);
      if (golfer == null) {
        redirect = true;
      } else {
        int golferIdValue = golfer.id;
      }
      // FindTeeTimesByDate requires the Course Id value to
       // be cast as an Int
int courseIdValue = Int32.Parse(courseIdString);
      selectedYear = Int32.Parse(Request.QueryString.Get("year"));
      selectedMonth = Int32.Parse(Request.QueryString.Get("month"));
      selectedDay = Int32.Parse(Request.QueryString.Get("day"));
      System.DateTime dateValue = System.DateTime.Parse
(Request.QueryString.Get("month")+"/"
+Request.QueryString.Get("day")+"/"
+Request.QueryString.Get("year"));
      tt = gs.FindTeeTimesByDate(courseIdValue,dateValue);
      gc = gs.GetCourseDetail(courseIdString);
}

Listing 10-12 viewTeeTimes.aspx: Iterates over an array of TeeTimes for a specific GolfCourse . If a valid Golfer is not provided, append a <noRegister> node to the XML. Also, if a TeeTime is already registered, append a taken node to the XML.

<%@ Page language="c#" Codebehind="viewTeeTimes.aspx.cs"
AutoEventWireup="false" Inherits="TeeTimesClient.content.
viewTeeTimes" %>
<document>
  <header>
    <title>My Tee Times</title>
  </header>
  <section>
    <header>
      <title>Select a Tee Time</title>
    </header>
    <view>
      <description>
        <%
        <!--- redirect will be true if the username and
password provided to ViewTeeTimes.aspx.cs
failed validation --->
          if (redirect)
            Response.Write("Your username and password failed
validation. If you feel this is an error, please try logging in
again. If you are not yet a registered user, please click 'My Tee
Times' above and complete our registration Form.");
          else
            Response.Write("Please choose a tee time from the list below");
        %>
      </description>
      <TeeTimes>
        <courseId>
          <%=gc.id%>
        </courseId>
        <% if (redirect) {
         <!---If redirect is true, append noRegister node--->
          Response.Write("<noRegister/>");
          } else {
            <!---Otherwise, provide the golferId--->
   Response.Write("<golferId>"+golfer.id+"</golferId>");
          }
        %>
        <%
          System.DateTime thisTeeTime = new System.DateTime
(selectedYear,selectedMonth,selectedDay,8,0,0,0);
          System.DateTime lastTeeTime = new System.DateTime
(selectedYear,selectedMonth,selectedDay,17,0,0,0);
          int taken = 0;
          while(thisTeeTime.CompareTo(lastTeeTime) != 0)
          {
            thisTeeTime = thisTeeTime.AddHours(.5);
        %>
        <teeTime>
          <TeeTimesystemDate>
            <%=thisTeeTime.ToString()%>
          </TeeTimesystemDate>
          <date>
            <%= thisTeeTime.ToShortDateString() %>
          </date>
          <time>
            <%= thisTeeTime.ToShortTimeString() %>
          </time>
          <%
            for(int i=0;i<tt.Length;i++)
            {
            <!---Compare this TeeTime to the TeeTime in this loop
            iteration of the TeeTimes array tt. If they are equal,
            the TeeTime is already registered for, append a taken node
            to the XML --->
              string myTime = thisTeeTime.ToShortTimeString();
              string takenTime = tt[i].time.ToShortTimeString();
              if (myTime.Equals(takenTime))
              {
                      %>
          `				<taken />
                      <%
              }
            }
          %>
        </teeTime>
        <%
           }
         %>
      </TeeTimes>
    </view>
  </section>
</document>

Figure 10-8 shows the results of Listing 10-12.

Figure 10-8 ViewTeeTimes.aspx displays a list of available tee times for a given course, and if a valid username and password combination is provided, it provides a link to immediately register for the given tee time.

The Controller and ie5.xsl

Listing 10-13, the controller.asp file, is obviously the heart of the Golf Reservation System client application, at least for the UI portion. The controller accepts every request from the client, implements the functionality requested, receives the result XML, and transforms it using the selected skin (in our case, ie5.xsl) and delivers the result to standard out. For detailed information about the controller design pattern, see Chapter 9, which describes it in detail. If you're already familiar with the controller design pattern, notice that the Golf Reservation System client application implements a standard version of the pattern.

Listing 10-13 controller.asp: The file responsible for loading the view XML and transforming it with the XSLT skin.

<%
  // Get the view argument
  // Define root for client application
  view   = Request.QueryString("view")
   base   = Request.ServerVariables("SERVER_NAME")+
Replace(Request.ServerVariables("URL"),"controller.asp","")
   if Request.ServerVariables("HTTPS") = "ON" Then
     base = "https://"+base
   else
     base = "http://"+base
   end if
  xmlurl = base+"content/"+view+".aspx?"
  arguments = ""
  // Parse additional arguments
  For Each item in Request.QueryString
    if arguments <> "" then arguments = arguments+"&"
    itemvalue = Request.QueryString(item)
    arguments = arguments+item+"="+itemvalue
  Next
  // Append the argument string to the xmlurl
  xmlurl = xmlurl+arguments
  // Construct a URL to the appropriate skin
  // This implementation uses only the Internet Explorer 5.0 skin
  // Other skins could be used based on the browser type
  if Request.QueryString("skin") <> "" then
    skin = Request.QueryString("skin")
  else
    skin = "ie5"
  end if
  xslurl = base+"xslt/"+skin+".xsl"
  // Load the XML
  // A production implementation should use the multi-threaded
version of the DomDocument.
  error = false
  Set source = Server.CreateObject("MSXML2.DOMDocument")
  source.async = false
  tmp = source.setProperty("ServerHTTPRequest", true)
  source.load(xmlurl)
  Set e = source.parseError
  if e.errorCode <> 0 then
    Response.write(e.reason)
    // Display error if exists
 if e.line > 0 Then
      Response.write(e.line)
      Response.write(" ")
      Response.write(e.linepos)
      Response.write(" ")
      Response.write(e.srcText)
    end if
    error = true
  end if
  // Load the XSLT
  Set style = Server.CreateObject("MSXML2.DOMDocument")
  style.async = false
  tmp = style.setProperty("ServerHTTPRequest", true)
  style.load(xslurl)
  Set e = style.parseError
  if e.errorCode <> 0 Then
    Response.write(e.reason)
    // Display error if exists
    if e.line > 0 Then
      Response.write(e.line)
      Response.write(" ")
      Response.write(e.linepos)
      Response.write(" ")
      Response.write(e.srcText)
    end if
    error = true
  end if
  if error = false Then
    xmlresult = source.transformNode(style)
  end if
  Response.write(xmlresult)
 %>

Listing 10-14, the ie5.xsl file, is the only XSLT skin used in this client application. Describing the complete functionality of ie5.xsl in detail is nearly impossible. Basically, this document is loaded with each call to controller.asp (for a larger production application, splitting the skin amongst multiple includes would be a better idea), and is responsible for transforming the XML result returned from .aspx files. The result of the XSLT is, in this case, an HTML document available for viewing in a Web browser. Golf Reservation System Client utilizes only this skin, but could conceivably utilize numerous skins, both for different interface designs as well as for different client devices.

XSLT and its associated technologies are constantly evolving. You can find the latest specifications for XSL, XSLT, and XPath at http://www.w3.org/Style/XSL/ .

The initial block of content loaded with each call to ie5.xsl is not template-specific. That is, it's included in the result tree every time. This block is where we place document declarations, such as the <html> and <head> nodes, as well as any scripts or style sheets that need to be included in each result.

Listing 10-14 ie5.xs: The XSLT Responsible for transforming the result view XML.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="no" />
  <xsl:strip-space elements="*" />
  <xsl:template match="document">
    <html>
      <head>
The css styles for ie5.xsl are included with each view in the
application at the location css/style.css. Any header/title node can
be inserted as the HTML title node.
        <link rel="stylesheet" type="text/css" href="./css/style.css" />
        <xsl:if test="header/title">
          <title>
            <xsl:value-of select="header/title" />
          </title>
        </xsl:if>

As discussed earlier, each device should include its own form validation mechanism to guarantee that required form inputs are satisfied and formatted correctly. The form validation script used in Golf Reservation System Client is located at js/formvalidate.js and is written using JavaScript. This file is extensive; displaying its entire contents in this chapter would be unreasonable. However, the form validation script could serve as an advanced lesson in developing JavaScript. This file is available on the accompanying CD.

In a production application, JavaScript is not recommended as the form validation method of choice. The primary reason is that the specification is not implemented with precision or consistency between browsers, and because browsers offer the user the ability to turn off this functionality. These arguments should also be taken into consideration while developing the UI. Rich Web UIs are often developed with extensive JavaScript, and therefore might result in unexpected functionality in some browser/platform combinations. For this client we assume the user has Microsoft Internet Explorer 5 or later. Our ie5.xsl includes extensive use of JavaScript, both in the UI and in form validation, and thus segregates those users without standards-based browsers and those who simply choose to turn off JavaScript.

  <script src="js/formvalidate.js" type="text/javascript"></script>

The following JavaScript functions are used in CourseDetail.aspx to create a rich UI for viewing course detail information and for allowing the user to switch between content sections without requesting new data from the server. Because CourseDetail page includes three different contentSections, the XML is present and available to the user with a single call to the page. Therefore, the UI designer must find a way to utilize that bulk of information in a manner that is intuitive for the user. In Golf Reservation System Client this is achieved using a tabular format and an expandable and collapsible list grouping.

When including content in the XSLT style sheet that you predict might be poorly formed (that is, the content could not satisfy an XML parser), you need to wrap that content between the following tags: <![CDATA[ poorly-formed XML content goes here ]]>. The following JavaScript is an example. Without the CDATA declaration, ie5.xsl would fail parsing.

The JavaScript function showTab is responsible for the toggling behavior of the tabs present in the CourseDetail page. Each courseDetailBlock is presented as a tab, and clicking on the header will show that courseDetailBlock and hide the others.

        <![CDATA[
       function showTab(section, scnt) {
            for (i=0; i < scnt; i++) {
              t = document.getElementById("s_"+i);
              if (t) {
                if (i == section) {
                  t.style.display = '';
                } else {
                  t.style.display = 'none';
                }
              }
            }
          }

The JavaScript function ExpandCollapse is responsible for the toggling behavior of the line items created by the individual Tee descriptions in the CourseDetail page. Each Tee object is presented as an expandable division, and clicking on the division will expand or collapse it.

          function ExpandCollapse(c1,ca) {
            var ca_a    = ca.split(',');
            var c1_a   = c1.split(',');
            c1 = document.getElementById(ca_a[1]);
            if (c1.style.display == 'none')
              expand = true;
            else
              expand = false;
            thePlus = ca_a[0]+"_plus_image";
            plus_div = document.getElementById(thePlus);
            if (expand)
            {
              if (plus_div)
                plus_div.src = "./images/minus.gif";
              for (i=0; i < c1_a.length; i++)
              {
                theRow = document.getElementById(c1_a[i]);
                theRow.style.display = '';
                plus_div = document.getElement
ById(c1_a[i]+"_plus_image");
                if (plus_div)
                  plus_div.src = "./images/plus.gif";
              }
            } else {
              if (plus_div)
                plus_div.src = "./images/plus.gif";
              for (i=1; i < ca_a.length; i++)
              {
                theRow = document.getElementById(ca_a[i])
                theRow.style.display = 'none';
              }
            }
          }
       // ]]>
      </script>
      </head>
      <body topmargin="0" leftmargin="0">
The class attributes throughout ie5.xsl refer to CSS style classes defined in the css.style.css file.

In order for HTML content to satisfy the XML parser, the HTML developer should use double quotes for attributes, close all tags (that is, using <br/> instead of <br>), and take note of case sensitivity. An exciting development by the W3C is the ongoing development of XHTML—HTML 4 in the form of valid XML. Conforming to the latest specification of XHTML ensures that your XHTML content will satisfy compliant XML parsers.

The initial division in ie5.xsl with the class "document" is included with each view in the application. This division encapsulates the entire viewable HTML content. Also included with each page in the application is the masthead table, which displays the name of the organization.

<div class="document">
<table class="main" width="600" align="left" cellpadding="0"
cellspacing="0" bgcolor="ffffff">
            <tr class="header">
              <td>
                <a class="header" href="./index.asp">Golf Reservation System</a>
              </td>
            </tr>
            <tr height="1" bgcolor="000000">
              <td>
                <img src="./images/spacer.gif" height="1" />
              </td>
            </tr>
            <tr>
              <td align="left">
                <table cellpadding="2" cellspacing="2" border="0">
                  <tr>
                    <td>

All anchor references and form posts resolve to controller.asp with a view argument appended (except for links to index.asp). The requirement to do so is defined by, and consistent with, the controller design pattern.

Like the initial document division and masthead table, a table of links is also included with each view in the application. This table provides links to the CourseSearch and Login pages.

                      <a class="navItem"
href="./controller.asp?view=login">My Reservations</a>
                    </td>

All entity references must be well-formed. For example, the common non-breaking space reference in HTML, &nbsp;, will fail an XML parser. Instead, you must fully qualify entity references: &nbsp; becomes &#160;.

The complete set of entity reference values as of HTML 4 can be found courtesy of the W3C at http://www.w3.org/TR/html4/sgml/entities.html .
  <td>&#160;|&#160;</td>
  <td>
                      <a class="navItem" href=
"./controller.asp?view=courseSearch">Course Search</a>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
            <tr height="1" bgcolor="#000000">
              <td>
                <img src="./images/spacer.gif" height="1" />
              </td>
            </tr>
            <tr>
              <td>
                <table width="100%" cellpadding="2" cellspacing="2" border="0">
                  <tr>
                    <td class="text">

The following apply-templates call sets off the xsl:template calls based on the contents of the XML being transformed. From here, each page takes a different path through the XSLT.

Apply-templates instruct the XSLT parser to match all source children nodes of the current node with appropriate template matches provided in the XSLT. For more information on apply-templates and other XSL/XSLT functionality, visit the XSL home page at the W3C located at http://www.w3.org/Style/XSL/ .

                      <xsl:apply-templates />
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
        </div>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="section">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="section/header">
    <table width="100%" cellpadding="2" cellspacing="2"
class="sectionHeader">
      <tr>
        <td>
          <xsl:value-of select="title" />
        </td>
      </tr>
    </table>
  </xsl:template>
  <xsl:template match="view">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="description">
    <table cellpadding="2" border="0" class="text">
      <tr>
        <td>
          <xsl:value-of select="." />
        </td>
      </tr>
    </table>
  </xsl:template>

For property/value pairs defined in XML we simply display them side by side in a table, with property description in one table cell and property value (or an input, whichever is appropriate) in another. This is an example of presentation-specific XSLT, as opposed to object-specific. The XSLT here has no context of the object it displays. The majority of the forms in Golf Reservation System Client use this approach because the UI for a form is rarely tied to an object in the system; it is simply used to solicit input from the user.

Here we begin to see the power and simplicity provided by XSLT. For every form in the client application the ASPX developer only needed to wrap <input> nodes with a <property> node. Here in ie5.xsl, one template match suffices each <property> node in the entire application: only three lines of code are necessary. Each input type is implemented differently, but <property> nodes are all implemented the same.

  <xsl:template match="properties">
    <table cellpadding="2" border="0">
      <xsl:apply-templates />
    </table>
  </xsl:template>
  <xsl:template match="property">
    <tr>
      <td>
        <font class="text">
<xsl:value-of select="@description" />
&#160;&#160;</font>
      </td>
      <td>
        <font class="text">

The xsl:choose element is provided by XSLT as an if-then-else type statement. The XSLT developer must use his or her discretion on when to use xsl:choose instead of an apply-templates or an xsl:if. Apply-templates is almost always preferred beacuse it takes advantage of the recursive nature of XSLT. Sometimes, such as when we choose whether or not to display an anchor reference for the property, understanding the code is easier if you use an xsl:choose.

This xsl:choose decides if the property value should be an anchor reference based on the existence (or nonexistence) of an href attribute of the current <property> node.

          <xsl:choose>
            <xsl:when test="@href">

To output a value, we can use <xsl:value-of select="node">, or we can imply that value-of by wrapping the node name with braces. In the href case we output the value of the attribute href using braces.

              <a href="{@href}">
                <xsl:apply-templates />
              </a>
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates />
            </xsl:otherwise>
          </xsl:choose>
        </font>
      </td>
    </tr>
  </xsl:template>

The form template match is used on every form in the Golf Reservation System client application and includes JavaScript for input validation. The JavaScript ensures that all inputs with the mandatory attribute set to yes are validated before the form is submitted. Also notice that, as in login.aspx, the ASPX developer can place multiple forms on a single page. The validation still works as each form is assigned a unique identifier through the XSLT function generate-id( ).

The JavaScript function validateForm is good example of using XSLT to create executable code at runtime. ValidateForm is an extremely generic JavaScript function but works well in validating all types of HTML inputs. Notice the use of xsl:for-each. The xsl:for-each element is a looping construct provided by XSLT that executes for each node qualifying for the select attribute. For each textarea node with an attribute of mandatory set to yes, for example, this XSLT creates a JavaScript validation routine that ensures that a value is submitted for that input. If no value exists, the JavaScript sets the browser focus on that input and returns false to the onSubmit call from the form. In doing so it stops the form from being submitted and instructs the user to enter a value.

  <xsl:template match="form">
    <script type="text/javascript">
      function validateForm_<xsl:value-of select="generate-id()"
/>(thisForm)
      {
        <xsl:for-each select="//input[@type='textarea']">
          thisForm.<xsl:value-of select="@name" />.value =
document.getElementById('d<xsl:value-of select="generate-id()"
/>').innerHTML;
        </xsl:for-each>
        <xsl:for-each select="//*[@mandatory='yes']">
        <xsl:choose>
          <xsl:when test="@type = 'text' or @type='textarea'">
              if (!hasValue(thisForm.<xsl:value-of select="@name"
/>,"text"))
              {
                alert("<xsl:value-of select="@name" /> is mandatory.");
                var the_field = thisForm.<xsl:value-of select="@name" />;
                if(the_field.style.display != "none" &amp;
!the_field.disabled &amp; !the_field.readOnly &amp;
!the_field.editableDiv)
                  the_field.focus();
                return false;
              }
            </xsl:when>
          <xsl:when test="name() = 'choice'">
              if (!hasValue(thisForm.<xsl:value-of select="@name"
/>,"select"))
              {
                alert("<xsl:value-of select="@name" /> is mandatory.");
                var the_field = thisForm.<xsl:value-of select="@name" />;
                if(the_field.style.display != "none" &amp;
!the_field.disabled &amp; !the_field.readOnly &amp;
!the_field.editableDiv)
                  the_field.focus();
                return false;
              }
            </xsl:when>
        </xsl:choose>
      </xsl:for-each>
        <xsl:for-each select="//input[@datatype]">
          if (!isoftype(thisForm.<xsl:value-of select="@name"
/>.value,'<xsl:value-of select="@datatype" />'))
          {
            alert("<xsl:value-of select="@name" /> is not in
<xsl:value-of select="@datatype" /> format");
            return false;
          }
        </xsl:for-each>
        <xsl:choose>
        <xsl:when test="@onSubmit">
            var rtnval = <xsl:value-of select="@onSubmit" />;
            return rtnval;
          </xsl:when>
        <xsl:otherwise>
            return true;
          </xsl:otherwise>
      </xsl:choose>
      }
    </script>

Here the XSLT builds a form tag here and includes an event handler for the onSubmit action, requiring validation before the form is submitted. The apply-templates call in the middle of this routine matches all <property> nodes and all <input> nodes of the source XML. This, again, is a good example of code reuse in XSLT: each form in the entire application is passed through this transformation and you need only write a few lines of code to build every form.

    <form action="{@action}" method="get" onSubmit="return validateForm_{generate-id()}(this)">
      <xsl:for-each select="@*[name() != 'action']">
        <xsl:copy>
          <xsl:value-of select="." />
        </xsl:copy>
      </xsl:for-each>
      <xsl:apply-templates />
      <input style="cursor:hand" type="submit" value=" submit " />
    </form>
  </xsl:template>

Next we build the HTML input types for each <input> node in the XML. The <input> nodes are present in the source view XML and are more or less copied in their initial format.

  <xsl:template match="input[@type='text']">
    <input type="text" name="{@name}" value="{@value}" class="text" style="border:inset  px">
      <xsl:for-each select="@*[name() != 'name' and name() != 'value']">
        <xsl:copy>
          <xsl:value-of select="." />
        </xsl:copy>
      </xsl:for-each>
    </input>
    <xsl:if test="@mandatory = 'yes'">
      <font class="error">&#160;*</font>
    </xsl:if>
  </xsl:template>
  <xsl:template match="input[@type='hidden']">
    <input type="hidden" name="{@name}" value="{@value}">
      <xsl:for-each select="@*[name() != 'name' and name() != 'value']">
        <xsl:copy>
          <xsl:value-of select="." />
        </xsl:copy>
      </xsl:for-each>
    </input>
  </xsl:template>

For the <textarea> nodes, the XSLT developer can take advantage of Internet Explorer's content-editable division functionality. The XSLT could simply display a <textarea>, but instead it displays a division with content-editable set to true so users can copy and paste images and the like from any application instead of being restricted to entering only text. When the form is submitted the validation script reads the current contents of this division and sets a form variable equal to that value, and then the form is submitted. (This functionality is available only in Internet Explorer 5.5 or later and will not work in other browsers.)

The following code matches the <textarea> node in the source XML and creates a division with a content-editable attribute set to true. It also chooses the size dimensions of the division based on a size attribute of the <textarea> node in the source XML. It does so using xsl:choose.

  <xsl:template match="input[@type='textarea']">
    <div id="d{generate-id()}" contentEditable="true" class="text">
      <xsl:choose>
        <xsl:when test="@size='small'">
          <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:150</xsl:attribute>
        </xsl:when>
        <xsl:when test="@size='medium'">
          <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:250</xsl:attribute>
        </xsl:when>
        <xsl:when test="@size='large'">
          <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:350</xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:350</xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:value-of disable-output-escaping="yes" select="value" />
    </div>
    <input type="hidden" editableDiv="yes" name="{@name}" value="{@value}" />
    <xsl:if test="@mandatory = 'yes'">
      <div style="display:inline">
        <font class="error">&#160;*</font>
      </div>
    </xsl:if>
  </xsl:template>

The <choice> node, as discussed earlier, is an excellent example of providing the XSLT developer room to design the appropriate UI. Specifically, the <choice> node can result in either a set of radio buttons, a set of check boxes, a single-select select box, or a multiple-select select box. The decision of which to use is made at runtime.

The xsl:choose statements count the number of options in the source XML and decide what type of form inputs to insert into the result document. xsl:if is then used to decide whether multiple choices will be allowed based on the existence or nonexistence of the allowmultiple attribute in the <choice> node in the source XML.

  <xsl:template match="choice">
    <xsl:choose>
      <xsl:when test="count(option) > 5">
        <select class="text">
          <xsl:for-each select="@*[name() != 'mandatory']">
            <xsl:copy>
              <xsl:value-of select="." />
            </xsl:copy>
          </xsl:for-each>
          <xsl:if test="@allowmultiple = 'yes'">
            <xsl:attribute name="multiple">yes</xsl:attribute>
            <xsl:attribute name="size">5</xsl:attribute>
          </xsl:if>
          <option value="">Please Choose Below</option>
          <xsl:for-each select="option">
            <option>
              <xsl:attribute name="value">
                <xsl:value-of select="@value" />
              </xsl:attribute>
              <xsl:if test="@selected='yes'">
                <xsl:attribute name="selected">
                  <xsl:value-of select="@selected" />
                </xsl:attribute>
              </xsl:if>
              <xsl:value-of select="." />
            </option>
          </xsl:for-each>
        </select>
      </xsl:when>
      <xsl:otherwise>
        <div style="display:inline">
          <xsl:for-each select="option">
            <xsl:choose>
              <xsl:when test="../@allowmultiple = 'yes'">
                              <input type="checkbox" name="{../@name}"
value="{@value}" style="border:0px" />
&#160;
<xsl:value-of select="." /><br />
              </xsl:when>
              <xsl:otherwise>
                              <input type="radio" name="{../@name}"
value="{@value}" style="border:0px" />
&#160;
<xsl:value-of select="." /><br />
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
        </div>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="@mandatory = 'yes'">
      <div style="display:inline">
        <font class="error">&#160;*</font>
      </div>
    </xsl:if>
  </xsl:template>

We begin the object-specific XSLT with the xsl:template match for course. In this case the template will only match for the CourseSearchResult result where the XSLT receives a list of courses as children of a <courseList> node. For each course we toggle the background color and provide links to courseDetail.

The template match for courseList provides an HTML table to surround the course list, supplies an apply-templates call to match all <courseListItem> nodes, and displays the number of <courseListItem> nodes in the last row of the table. The template match for <courseListItem> displays the name, city, and state for each node. The name is also provided with an anchor reference allowing the user to click through to get specific detail about that course. The link has an appended id argument as is required by the target view, courseDetail.

  <xsl:template match="courseList">
    <table class="text" width="100%" cellspacing="0" cellpadding="0" border="0">
      <xsl:apply-templates select="courseListItem" />
      <xsl:if test="count(courseListItem) != 0">
        <tr>
          <td colspan="3" height="1" bgcolor="#000000">
            <img src="./images/spacer.gif" height="1" />
          </td>
        </tr>
      </xsl:if>
      <tr>
        <td colspan="3" bgcolor="#ffffff" class="text">
          <xsl:value-of select="count(courseListItem)" /> course(s) satisfied your criteria
        </td>
      </tr>
      <tr>
        <td colspan="3" height="1" bgcolor="#000000">
          <img src="./images/spacer.gif" height="1" />
        </td>
      </tr>
    </table>
  </xsl:template>
  <xsl:template match="courseListItem">
    <tr>
      <xsl:choose>
        <xsl:when test="position() mod 2">
          <xsl:attribute name="style">
          background-color:#D1D7DC
        </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="style">
          background-color:#f1f1f1
        </xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <td class="text" valign="top">
        <a href="controller.asp?view=courseDetail&amp;id={id}">
          <xsl:value-of select="name" />
        </a>
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="city" />
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="state" />
      </td>
    </tr>
  </xsl:template>

The courseDetail xsl:template match employs the most extravagant use of HTML and DHTML in the Golf Reservation System client application. This match is selected for each call to courseDetail.aspx.

  <xsl:template match="courseDetail">
    <xsl:apply-templates select="courseDetailBlock" />
  </xsl:template>

In the XSLT defined in the following block of code, each courseDetailBlock is assigned a unique id as well as a displayed tab. This tab takes advantage of JavaScript defined previously in the XSLT file to dynamically hide and display different sections of content—in this case, different matches of the <courseDetailBlock> node.

  <xsl:template match="courseDetailBlock">
    <xsl:variable name="numsections"
select="count(../courseDetailBlock)" />
    <xsl:variable name="precedingsibs" select="count(
preceding-sibling::courseDetailBlock)" />
    <xsl:variable name="sectionid" select="concat('s_',
$precedingsibs)" />
    <table id="{$sectionid}" class="tabHeader" cellspacing="0"
cellpadding="2" align="center">
      <xsl:if test="$precedingsibs > 0">
        <xsl:attribute name="style">display:none</xsl:attribute>
      </xsl:if>
      <tr>
        <td colspan="{$numsections}" height="5">
          <img src="images/spacer.gif" height="5" />
        </td>
      </tr>
      <tr align="center">
        <xsl:for-each select="preceding-sibling::courseDetailBlock">
          <td align="center">
            <a id="button{position}" href="javascript:showTab({
position()-1},{$numsections})">
              <xsl:value-of select="@description" />
            </a>
          </td>
        </xsl:for-each>
        <td align="center" class="activeTab">
          <xsl:value-of select="@description" />
        </td>
        <xsl:for-each select="following-sibling::courseDetailBlock">
          <td align="center">
            <a id="button{position}" href="javascript:showTab
({$precedingsibs+position()},{$numsections})">
              <xsl:value-of select="@description" />
            </a>
          </td>
        </xsl:for-each>
      </tr>
      <tr>
        <td>
          <xsl:attribute name="colspan">
            <xsl:value-of select="$numsections" />
          </xsl:attribute>
          <table class="text" width="100%" cellpadding="0"
cellspacing="0" border="0">
            <tr>
              <td>

Each courseDetailBlock is applied with the apply-templates call in the following snippet.

                <xsl:apply-templates />
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </xsl:template>

The courseItem with attribute type set to courseInfo template match displays generic GolfCourse information such as the address and greens fee.

  <xsl:template match="courseItem[@type='courseInfo']">
    <xsl:apply-templates select="section" />
    <table class="text">
      <tr>
        <td rowspan="3" valign="top">Address:</td>
        <td>
          <xsl:value-of select="address/address1" />
        </td>
      </tr>
      <tr>
        <td><xsl:value-of select="address/city" />, <xsl:value-of select="address/state" /></td>
      </tr>
      <tr>
        <td>
          <xsl:value-of select="address/zip" />
        </td>
      </tr>
      <tr>
        <td rowspan="3" valign="top">Greens Fee:</td>
        <td>$<xsl:value-of select="price" /></td>
      </tr>
    </table>
  </xsl:template>

The holeInfo Block displays hole-specific information such as hole number, par, distance and handicap. It also matches the <tees/tee> node to provide tee-specific information.

  <xsl:template match="courseItem[@type='holeInfo']">
    <xsl:apply-templates select="section" />
    <table width="100%" cellspacing="0" cellpadding="0">
      <tr>
        <td valign="top" nowrap="yes">
          <table width="100%" cellpadding="0" cellspacing="1">
            <tr bgcolor="a0a0a0" style="padding:2px;spacing-top:0px">
              <td class="text" align="center" style="color:white;
font-weight:bold">
                Hole
              </td>
              <td class="text" align="center" style="color:white;
font-weight:bold">
                Par
              </td>
              <td class="text" align="center" style="color:white;
font-weight:bold">
                Distance
              </td>
              <td class="text" align="center" style="color:white;
font-weight:bold">
                Handicap
              </td>
            </tr>
            <xsl:apply-templates select="tees/tee" />
          </table>
        </td>
      </tr>
      <tr bgcolor="#336699">
        <td height="1">
          <img src="./images/spacer.gif" height="1" />
        </td>
      </tr>
    </table>
  </xsl:template>

For each tee associated with a GolfCourse object, this xsl:template match provides an expandable and collapsible division which provides information for the holes associated with the tee. The JavaScript necessary for expanding and collapsing these divisions is included in the result document and was listed previously in ie5.xsl under the function name ExpandCollapse.

  <xsl:template match="tee">
    <xsl:variable name="id" select="concat('v',generate-id())" />
    <tr>
      <xsl:attribute name="id">
        <xsl:value-of select="$id" />
      </xsl:attribute>
      <td colspan="4">
        <xsl:attribute name="id"><xsl:value-of select="$id" />_plus
</xsl:attribute>
        <table width="100%" cellpadding="0" cellspacing="0"
border="0" bgcolor="#e0e0e0" class="listgroup">
          <xsl:attribute name="id">
<xsl:value-of select="$id" />_table</xsl:attribute>
          <tr>
            <td width="5" style="cursor:hand">
              <img src="./images/plus.gif" hspace="4" style="border:1px outset">
                <xsl:attribute name="onClick">ExpandCollapse
('<xsl:for-each select="holes/*[name() =  'hole']">
                    <xsl:value-of select="concat('v',generate-id())" />
                    <xsl:if test="last() > position()">,</xsl:if>
                  </xsl:for-each>','<xsl:value-of select="$id"
/><xsl:for-each select=".//*[name() = 'hole']">,<xsl:value-of
select="concat('v',generate-id())" /></xsl:for-each>')</xsl:attribute>
                <xsl:attribute name="id"><xsl:value-of select="$id"
/>_plus_image</xsl:attribute>
              </img>
            </td>
            <td align="left" class="text">
              <xsl:value-of select="description" /> Tee: Distance:
<xsl:value-of select="distance" /> yards&#160;Slope: <xsl:value-of select="slope" />
            </td>
          </tr>
        </table>
      </td>
    </tr>
    <xsl:apply-templates select="holes" />
  </xsl:template>

Each tee contains its own list of holes, complete with number, hole, distance, handicap, and par. Here we iterate over each <hole> node and display that data, toggling the row color of each hole as we did with the courseList match previously.

  <xsl:template match="hole">
    <xsl:variable name="id" select="concat('v',generate-id())" />
    <tr>
      <xsl:choose>
        <xsl:when test="position() mod 2">
          <xsl:attribute name="style">
            background-color:#ffffff;display:none
          </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="style">
            background-color:#f1f1f1;display:none
          </xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:attribute name="id">
        <xsl:value-of select="$id" />
      </xsl:attribute>
      <td class="text" valign="top">
        <xsl:value-of select="@number" />
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="par" />
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="distance" />
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="handicap" />
      </td>
    </tr>
  </xsl:template>

The scheduleTeeTime block provides a table of available tee times that the user can view, and if no <taken> child node of the TeeTime and no <noRegister> ancestor node are present, the user is provided a link to the scheduleTeeTime page to register.

  <xsl:template match="courseItem[@type='scheduleTeeTime']">
    <xsl:apply-templates select="section" />
  </xsl:template>
  <xsl:template match="TeeTimes">
    <table class="text" width="100%" cellspacing="0" cellpadding="1" border="0">
      <tr bgcolor="#a0a0a0" style="padding:2px;spacing-top:0px">
        <td class="text" align="left" style="color:white;
font-weight:bold">
          Date
        </td>
        <td class="text" align="left" style="color:white;
font-weight:bold">
          Time
        </td>
        <td class="text" align="right" style="color:white;
font-weight:bold">
        Reserve It!
        </td>
      </tr>
      <xsl:apply-templates select="teeTime" />
    </table>
  </xsl:template>
  <xsl:template match="teeTime">
    <tr>
      <xsl:choose>
        <xsl:when test="position() mod 2">
          <xsl:attribute name="style">
          background-color:#D1D7DC
        </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="style">
            background-color:#f1f1f1
          </xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <td class="text" valign="top">
        <xsl:value-of select="date" />
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="time" />
      </td>
      <td class="text" valign="top" align="right">
        <xsl:choose>
          <xsl:when test="taken">
            Tee Time Reserved
          </xsl:when>
          <xsl:when test="../noRegister">
            login to register
          </xsl:when>
          <xsl:otherwise>
            <a>
            <xsl:attribute name="href">controller.asp?view=
scheduleTeeTime&amp;golferId=<xsl:value-of select="../golferId"
/>&amp;courseId=<xsl:value-of select="../courseId" />&amp;
teeTime=<xsl:value-of select="TeeTimesystemDate" /></xsl:attribute>
            Reserve Tee Time</a>
          </xsl:otherwise>
        </xsl:choose>
      </td>
    </tr>
  </xsl:template>

The <teeTimeDetail> node is processed with the scheduleTeeTime.aspx file. This node allows the user to select a tee time and register. Specifically, this template match displays the golfer's name, the course name with a link to the courseDetail page, and the selected tee-time data.

  <xsl:template match="teeTimeDetail">
    <table cellpadding="2" class="text">
      <tr>
        <td>Golfer:</td>
        <td>
          <xsl:value-of select="golfer/name" />
        </td>
      </tr>
      <tr>
        <td>Course:</td>
        <td>
          <a>
            <xsl:attribute name="href">
              controller.asp?view=courseDetail&amp;id=<xsl:value-of select="course/id" />
            </xsl:attribute>
            <xsl:value-of select="course/name" />
          </a>
        </td>
      </tr>
      <tr>
        <td>Date:</td>
        <td>
          <xsl:value-of select="date" />
        </td>
      </tr>
      <tr>
        <td>Time:</td>
        <td>
          <xsl:value-of select="time" />
        </td>
      </tr>
    </table>
  </xsl:template>

The MyTeeTimes template match is used exclusively by the viewMyTeeTimes.aspx page and lists the tee times registered by a validated user. The source XML provides <myTeeTimes> nodes with children nodes named myTeeTime containing data about each registered tee time. The <myTeeTime> node is called using apply-templates in the center of the table.

  <xsl:template match="myTeeTimes">
    <table class="text" width="100%" cellspacing="0" cellpadding="0" border="0">
      <tr bgcolor="#a0a0a0" style="padding:2px;spacing-top:0px">
        <td class="text" align="left" style="color:white;
font-weight:bold">
          Course
        </td>
        <td class="text" align="left" style="color:white;
font-weight:bold">
          Date
        </td>
        <td class="text" align="right" style="color:white;
font-weight:bold">
          Time
        </td>
      </tr>
      <xsl:choose>
        <xsl:when test="myTeeTime">
          <xsl:apply-templates select="myTeeTime" />
        </xsl:when>
        <xsl:otherwise>
          <tr>
            <td colspan="3">
              You have no tee times scheduled
            </td>
          </tr>
        </xsl:otherwise>
      </xsl:choose>
      <tr>
        <td colspan="3" height="1" bgcolor="#000000">
          <img src="images/spacer.gif" height="1" />
        </td>
      </tr>
    </table>
  </xsl:template>

The myTeeTime template matches a single tee time assigned to a validated user, providing a link to courseDetail for the associated golf course.

  <xsl:template match="myTeeTime">
    <tr>
      <xsl:choose>
        <xsl:when test="position() mod 2">
          <xsl:attribute name="style">
          background-color:#D1D7DC
        </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="style">
            background-color:#f1f1f1
          </xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <td class="text" valign="top">
        <a>
          <xsl:attribute name="href">
            controller.asp?view=courseDetail&amp;id=<xsl:value-of select="course/courseId" />
          </xsl:attribute>
          <xsl:value-of select="course/name" />
        </a>
      </td>
      <td class="text" valign="top">
        <xsl:value-of select="teeTime/date" />
      </td>
      <td class="text" valign="top" align="right">
        <xsl:value-of select="teeTime/time" />
      </td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

This XSLT file, ie5.xsl, is a large and extensive file providing complete template matches for each node in every source XML document possible within the Golf Reservation System client application. Maintaining this file is difficult because it includes a number of different languages: HTML, JavaScript, and XSLT. The developer creating this XSLT style sheet would therefore require working knowledge of all these languages and possibly more. A more modular approach might be in order for a production application where the JavaScript might be provided through an include instead of being present in the actual XSLT style sheet. You could also develop a client application that contains skins that span a number of documents. For example, if it is possible to separate form XSLTs from generic display transformations, it might be a good idea to develop those in separate files (perhaps named ie5_form.xsl and ie5_display.xsl).

Nonetheless, the UI developer must hold responsibility for not only being familiar with the Web Services available in building the .aspx and .aspx.cs files, but also for having knowledge of the various Web languages available for use. And given that the controller design pattern is flexible in cross-platform development, the UI developer or team would also require knowledge of markup for devices such as Wireless Application Protocol and others.

Therefore, while ie5.xsl is extensive and large, it is not a complete solution for a production application. Instead, it is an all-inclusive example of how one would use XSLT, HTML, and DHTML/JavaScript to create a browser-ready skin for a controller design pattern client in a .NET Framework client application.


Where to From Here?

The Golf Reservation System is a simple implementation of a distributed application using XML-based Web Services and employing full use of the .NET Framework, the controller design pattern, and Visual Studio .NET. But even simple XML Web services like GolfCourseService could be much more extensive as XML Web services can be called from any device connected to the Internet that can send, receive, and understand the SOAP protocol. Golf Reservation System Client could provide a skin for tee time registration over mobile phones, handheld PDAs, or any other device.

One interesting application using GolfCourseService might be to write a client using Microsoft Visual Basic for Applications and extending Microsoft Outlook to take advantage of the Golf Reservation System booking capabilities already provided in the service. Another option might be for the publishers of GolfCourseService to provide its golf course data to various golf-focused Web sites or provide its golfer data to golf club manufacturers.

The Golf Reservation System could also extend its own Web Service and client application to offer more functionality to its users, by keeping detailed information about how individuals performed in a given round, or by facilitating payment for the tee time registration with a Web method responsible for handling credit card transactions. As is evident, Windows .NET and XML Web services offer boundless opportunities for creating distributed applications for the Internet.

This concludes the section of the book on application development. In this section we have used a lot of the XML-based implementations present in .NET as well as other frameworks. We have built some real-world applications and we have covered many of the down-and-dirty details of XML.

In the next section of the book we will talk about interoperability. This will include chapters on developing for multiple platforms, integrating with legacy systems, and publishing content to multiple devices.


Platform Development

Every major hardware vendor hopes for some new technology that will motivate all customers to abandon their legacy systems and exclusively buy its hardware offerings. Every operating system and software vendor hopes for the same. Even developers must admit that their lives would be easier if they didn't have to design for different hardware platforms and operating systems, each with its own differences in language support, development environment, bandwidth, and display constraints.

The truth is that there will always be a variety of hardware platforms, operating systems, development environments, and operating constraints. Even in new application deployment, issues around integration of legacy systems always spring up. These varieties will continue to motivate designers to develop solutions that work with the set of hardware platforms and operating systems that exist in the developer's organization.

Some application development technologies present severe hardware or operating system constraints. Not so with Web Services. The entire Web Services concept is built around existing open standards like HTTP and XML. All a device needs to participate as a Web Service client is the ability to create an XML document programmatically and the ability to issue an HTTP POST and GET. This capability exists in any device that can run an HTML 3.2-aware Web browser and is relatively easy to implement in other devices (such as wireless hand-held computing devices, even with limited displays and no support for modern Web browsers).

The bar is higher for participating as a Web Services server, though not by much. A Web Services server must accept an HTTP POST or GET method, parse the XML document, marshal the data appropriately, perform the Web Service function, marshal the data back into a result XML document, and return the result document to the client via HTTP. Implementing Web Service servers on the majority of computing devices in existence today is definitely possible.

This chapter provides significant details about the issues faced by Web Service developers in building cross-platform applications. We discuss legacy mechanisms for building cross-platform applications, and then introduce the Web Services approach. The good news is that Web Services provide one of the easiest, most reliable means yet created to build full-featured, robust, cross-platform applications for business, consumer, and industrial applications.


Legacy Mechanisms for Cross-Platform Development

As long as computing systems have lived on networks, there has been a need for those systems to communicate with each other at the application level. Network protocols such as TCP and UDP provided a way for programmers to build in client/server and peer-to-peer communication. Programming these protocols directly, however, could be a daunting task, so layers appeared above these protocols that eased the task of writing distributed applications. In this section we review several of these layers that support cross-platform application development, including UNIX Sockets, the Distributed Computing Environment (DCE), CORBA, Java RMI, and DCOM.

UNIX Sockets

UNIX sockets might have been the first standard built on top of the basic protocols that provided an interface for programmers to write the communication component of client/server applications at a more abstract level. A programmer could specify a connection-oriented socket when a persistent connection was needed, or a connectionless socket for sending messages, and the socket would perform the right function with the underlying protocol.

Even with UNIX sockets, however, many problems remained for the application programmer. Although UNIX sockets presented a layer of abstraction above the underlying protocols, the socket interface still required low-level programming. As for platform interoperability, UNIX sockets work as long as you only want to communicate with UNIX systems. (Similar implementations for other platforms, like WinSock for Microsoft Windows, were developed later.)

UNIX sockets (and other related implementation) provided raw data-transfer capability only, and lacked the ability to invoke remote procedures or functions. Any processing of the incoming data stream had to be hard-wired into the client applications. Sockets provided no capability to, for example, pass method or function arguments between systems.

Distributed Computing Environment

While UNIX sockets provided the ability to pass monolithic data streams between systems, designers of distributed systems needed a more platform-independent mechanism that supported actual remote procedure calls. One attempt to provide RPC capability was known as the Distributed Computing Environment, or DCE. This environment provides a theoretically platform-independent infrastructure for implementing and managing distributed applications. It also provides directory and security services, as well as distributed file system and RPC capabilities (it even provided a time-synchronization mechanism for "cell" members). DCE also correctly handled the marshaling of RPC data as appropriate to match the expected data format of the calling or receiving OS.

Unfortunately, DCE was only platform-dependent to the extent that relatively heavyweight client software existed for the target platform. To participate in these services a platform had to be configured (again, a potentially complex operation) into a DCE cell. Later DCE implementations allowed the user to conFigure only certain services, such as the RPC mechanism. However, the overhead in purchasing, configuring, and maintaining a cell, as well as the lack of general availability of the client software, prevented DCE from becoming a general-purpose solution for distributed computing.

CORBA

CORBA provides a platform- and language-independent infrastructure for building distributed applications and invoking remote procedure calls from client systems. CORBA presents fine-grained distributed objects within heterogeneous computing networks. Applications can identify and use available business software components via a network of ORBs. The standards body behind CORBA, the Object Management Group (OMG), originally believed that large numbers of ORBs would exist on the Internet, transparently channeling object requests to the appropriate server resource.

CORBA objects are essentially language-independent. CORBA servers present their interfaces in the form of an IDL, which defines the methods supported by the CORBA server and the expected arguments to those methods. Client programs interact with these objects via stub interfaces that execute an object's methods remotely on the machine hosting the software. The ORB sits in the middle and dispatches all intermachine communication. ORBs around the Internet can share information about the objects they manage. The IIOP was developed for this purpose.

Java RMI

Like CORBA, Java's RMI subsystem allows objects to be distributed across a network. Unlike CORBA, Java RMI can transport an entire class, software and all, across application boundaries (via either RMI or IIOP protocols). This communication is possible because all Java applications must run on the Java Virtual Machine. This enables distributed Java applications to safely download compiled byte streams and execute them locally.

Although the ability to share executable objects dynamically over the Internet opens some exciting possibilities, Java RMI has its shortcomings. Most notably, the technology only works if the applications involved in the exchange are both written in Java. Programs written to use RMI also require a fair amount of preparation. Remote stub classes need to be generated statically or downloaded dynamically for remote methods to work properly. Finally RMI uses a proprietary protocol to exchange information, which means more server software and firewall tuning.

DCOM

DCOM is a Windows implementation of a distributed-application development environment, based on Microsoft's COM. DCOM uses DCE RPC as the underlying remote object-support mechanism.

Unlike CORBA, a DCOM object can present multiple interfaces, which in turn can provide multiple object behaviors. A COM client invokes a COM object by acquiring a pointer to the object's interface. The client invokes methods through that pointer; from the perspective of the client, the object appears to reside in the client's address space. Because DCOM is tightly integrated with the Windows operating system, DCOM provides a solution for Windows-based distributed applications only. Table 11-1 provides a comparison of the several distributed-application infrastructures.


Building Cross-Platform Applications Using Web Services

Thousands of distributed applications have been implemented using these legacy mechanisms. However, each of these mechanisms presents one or more serious obstacles in the areas of platform- or language-dependence, ease of configuration, ease of implementation, or cost. No mechanism meets the need for a truly platform- and language-independent mechanism that is easily deployable on top of existing infrastructures and transport protocols, with either no or few trivial configurations required of participating systems. No mechanism met those needs; that is, before Web Services.

Table 11-1 Comparison of Distributed Application Infrastructures
Type | OS/Platform | Language Support | Configuration Complexity | Cost
Sockets | UNIX (variants available) | C | High | None
DCE | UNIX | C, C++ | High | High
CORBA | UNIX, Windows | Any | Medium | Medium
Java RMI | Java (Java Virtual Machine) | Java only | Low | None
DCOM | Windows | Many | Low | None

Building Servers

Let's discuss some of the issues around building Web Services on the most popular computing platforms, including Microsoft Windows NT, Microsoft Windows 2000, UNIX, and Linux. We will divide our discussion between building Web Service servers and Web Service clients.

Simplistically speaking, the only requirement for providing a Web Service server is the ability to present that service as a URL. The URL must accept incoming service requests over HTTP as POST or GET methods or XML SOAP documents, must marshal the data within the request appropriately for its OS, must invoke appropriate methods on the data, repackage the data into an XML result document, and return the document via HTTP to the client.

If you want to advertise your Web Service and make it available to clients throughout the Internet, you must create a Web Services Description Language (WSDL) document that describes your Web Service to clients. The WSDL document is an XML document that provides information about the Web Service's exposed methods, arguments to those methods, and return values. You would typically register your WSDL document with a Web Service registration service, such as UDDI.org, which we mentioned in Chapter 8.

Web Services present clear advantages when compared to other cross-platform application development solutions. Because Web Services are built on existing, open technologies such as HTTP and XML, developers must implement little, if any, new infrastructure. Virtually all modern operating systems and platforms are supported. The Web Service server is still based on a Web server running a Web application server. Language support is broad. Complexity is low; the Web Service designer need only make the paradigm shift that the Web server is providing services, not Web pages, via a URL.

Visual Studio .NET

You can, theoretically, create your own HTTP GET or POST methods or build your own SOAP files for transmitting Web Services requests between client and server. Why bother doing so, however, when a development environment can support you by doing many of these tasks automatically? If you are developing on a Windows server platform, you can draw on the powerful capabilities of the Microsoft Visual Studio .NET environment for building XML Web service applications.

Visual Studio .NET handles many of the mundane tasks associated with developing Web applications, such as creating SOAP documents appropriate for your XML Web service methods, marshaling and unmarshaling data, creating WSDL contracts for your Web Service methods, and ensuring that these methods are stateless by encapsulating all application state data within the SOAP document. With Visual Studio .NET you can quickly build XML Web service applications that are reliable and easily scalable.

Visual Studio .NET provides support for most modern Web development languages, including VBScript, JScript, Visual Basic, C, C++, Visual C++, C#, Perl, Python, COBOL, PASCAL, and Scheme. It also provides an integrated environment for XML Web service development appropriate for developers from different development backgrounds.

Finally Visual Studio .NET provides rapid prototyping capabilities for client user interfaces. For debugging and testing, Visual Studio .NET provides the ability to view and trace SOAP messages between client and server.

UNIX and Linux

Other operating systems and language vendors provide toolkits and development environments for creating Web Service servers on other platforms, including UNIX and Linux operating systems. Options include IBM's Web Services Toolkit and the Java 2 Enterprise Edition (J2EE).

J2EE provides several APIs to specifically support the needs of Web Service application developers. Table 11-2 presents each of these API groups.

Table 11-2 Java APIs for Web Services
API | Description
JAXP: Java API for XML processing | Provides the capability for both tree-based processing (DOM) or event-based processing (SAX) of XML data. The API also provides support for XMLTs should you want to transform your XML documents into another XML vocabulary. You might want to do this to, for example, transform XML data to HTML for display in a Web browser, or extract components of an XML document and apply WML wrappers for display on the screen of a wireless device.
JAXB: Java API for mapping XML documents to Java classes | Provides the capability for creating Java classes from XML data and an XML DTD or Schema. These classes automatically provide the capability to validate, marshal, and unmarshal the XML data from an XML document instance. From an XML document you can create a Java object tree for processing by your Web Service server.
JAXM: Java API for messaging | Provides the capability to create SOAP documents for passing messages between systems in SOAP format. The API handles details like SOAP syntax and message identification.
JAXR: Java API for XML registries | Supports accessing standard Internet-based business registries. The JAXR API provides the capability to register a Web Service with registries such as UDDI.org, as well as the capability to search registries such as UDDI.org for specific Web Services.
JAX-RPC: Java API for issuing RPCs via XML documents | Supports issuing RPCs as request and result SOAP documents. JAX-RPC handles the details of marshaling and unmarshaling the data into the SOAP document JAX-RPC provides. JAX-RPC is a less-general API than JAXM. It does not provide all the capabilities of JAXM, including asynchronous messaging, multiparty message routing, and delivery verification.

Other Platforms

Theoretically, any computing device that can accept incoming HTTP requests, manipulate incoming data by a method, marshal results into an XML document, and deliver the result XML document to the client via HTTP can serve as a Web Service server. Variables in choosing to perform these tasks on a non-Windows or non-UNIX platform include the nature of the Web Service and the availability of tools to support providing the Web Service infrastructure. For example, at a minimum the platform should support an XML parser for creating the XML result file.

Alternative platforms that support the J2EE might be reasonable candidates for Web Service servers. Otherwise, deploying a Web Service server on, for example, a handheld device, might be an exercise in non traditional application development, at least for the near future.

Building Clients

Building clients for distributed applications has never been easier. Because Web Services are based on open standards like HTTP and XML, all that is minimally required of a Web Service client is the ability to create and pass to the server an HTTP GET or POST request or an XML SOAP document. This capability can be implemented in a variety of ways. Most simply, any client that supports an HTML 3.2-compatible Web browser and the HTTP protocol can participate as a Web Services client. Even clients that do not support HTML 3.2-compatible browsers can use their own built-in application support to create the requisite XML files for transmission over HTTP to the Web Services server.

One of the first steps in designing a Web Service client is to inspect the WSDL document for the Web Service you want to run. The WSDL document is an XML document that provides the methods exposed by the Web Service, expected arguments, and return values. You can perform this inspection manually, but a Web Service development environment can make the task easier for you.

Windows Platforms

Visual Studio .NET provides a complete development environment for building, deploying, and testing XML Web services. You can develop both the XML Web service server and client portions directly in the application. Clients that run on Windows NT, Windows 2000, or Windows XP can be HTML or Web forms. On a Windows platform Visual Studio .NET can save the Web form as a DLL.

UNIX and Linux

Again, you can build an HTML 3.2-based Web Service client for UNIX and Linux platforms directly from Visual Studio .NET. Because the client interface to a Web Services server is only an HTTP GET or POST method or an XML file, you can design your own Web Service client. The language that you use and the form of the Web Service client is irrelevant as long as the client can marshal its native data format in an XML file (or arguments to an HTTP method), send the data over the wire via HTTP, receive the result XML document via HTTP, and remarshal the results.

Other Platforms

Again, Visual Studio .NET provides the capability to develop and test XML Web service clients on other devices, including devices running Microsoft Windows CE. These devices include portable hand-held computing devices, "smart" devices such as tablet PCs, portable game consoles, and digital television controllers. As the number and variety of these devices increases, so will the opportunity to take advantage of XML Web services via these devices.

A key point in designing and implementing Web Services is that the Web Service client need not be a Web form driven by a human user. Certainly, any computing device can act as a Web Service client. In fact, Web Service servers can invoke methods that call other Web Services in completing a client request. For example, a digital television controller can invoke a Web Service that provides video-on-demand capabilities. This service might, in turn, invoke a Web Service that performs billing functions. Keep this capability in mind, especially as you develop applications for non-traditional computing devices.

Accessing Objects Across Platforms

Object access and sharing presents some of the major challenges in distributed-application development. All but the most homogeneous environments require that objects be converted from an application's native data format, passed along a wire, and converted back to a different application's (possibly different) native data format. The process must be repeated, in reverse, at the return of data by the server. Many solutions have been developed for this problem, although none have thus far presented a mechanism as open and as widely deployable as that presented by Web Services.

Traditional Methods

Each of the traditional mechanisms for sharing objects across platforms present limitations that have prevented these mechanisms from supporting Internet-wide distributed applications. CORBA objects are fine grained compared to the granularity needed to support business functions. RMI allows sharing of objects and code, but only between Java platforms. DCOM only allows object sharing between Windows platforms. Full-blown DCE is too expensive and too complex, and UNIX sockets require programming at a lower level than many developers are comfortable with, making development and maintenance problematic.

Furthermore, it became apparent that data, not code, was the most precious commodity in Internet applications. The HTTP protocol had existed for years and firewalls had been configured to cope with it. XML proved to be a powerful vehicle for transporting any type of information. There must be some way to combine the two technologies and produce the next big advance in distributed computing.

Web Services

Web Services perfectly fit the reality that data, not code, is the most important commodity in distributed applications. Because Web Services are based on the open standards of HTTP and XML, they offer an infrastructure for distributed-application development free of the barriers that prevented widespread deployment of past systems.

To share objects in a Web Services environment you must convert the instantiated objects from their memory storage to a format that can be transmitted across a network. Because HTTP is a stateless protocol, the serialization process must include all state or property information. The serialized object must provide enough information to be reassembled into an identical object by an application at the other end of the wire.

Both Visual C++ and Java support object serialization. However, each language uses its own data format to store its objects and metadata in a serialized format. This distinction greatly complicates the task of sharing serialized objects between applications written in different languages.

By using XML as a common serialization format, any applications that can parse an XML file can instantiate objects serialized within an XML file. Any application that can parse an XML file and marshal the contents into and out of the application's native object format can share objects in and participate in a Web Services environment. Using XML as a common serialization format ensures that the widest possible set of computing platforms can participate in a Web Services environment.

Writing Objects for Multiple Platforms

Object serialization presents one of the more challenging problems in implementing distributed applications. How can you transform an object from the internal data structures created and maintained by your programming language to a persistent form that can be passed between server and client over the wire?

This problem appears in two locations in Web Service applications. The client must serialize its data to the argument list to an HTTP POST or GET method or to a SOAP document. The Web Service server system must both un-serialize the incoming request and reserialize the result to an XML (possibly XML SOAP) result document.

Fortunately, you don't need to worry about the details of object serialization. Web Service development environments and APIs designed to provide Web Service functionality have this capability built in. Visual Studio .NET allows you to serialize XML Web service arguments within a SOAP document. Other APIs, such as J2EE's JAXM, directly support the creation of SOAP documents.

After an object is serialized within a SOAP document, what do we do to ensure the interoperability of the resulting SOAP document within a distributed application? Nothing. The XML and SOAP specifications ensure that any computing device with any internal data format will interpret the content of the SOAP document in exactly the same way. Any platform that supports an XML parser will appropriately read and marshal any data contained within the SOAP document.


Issues in Object Development and Multiple-Platform Design

This section provides insights into issues about designing Web Service objects. Web Service objects present some special constraints, such as the requirement that they be passed by value and that Web Service methods tend to present much more course-grained functionality than do other distributed-application paradigms. We also discuss application development issues, such as the application development environment, presented by the major hardware platforms, and review the general issues of security, scalability, and state maintenance.

Object Development

We mentioned limitations of Web Service objects in Chapter 8. All Web Service objects are passed by value through HTTP and SOAP. No mechanism for passing an object by reference exists. Calls to Web Services require some overhead. Objects must be marshaled into (potentially large) XML SOAP documents and return results must be unmarshaled. Web Service calls, because they rely on HTTP, are stateless. Because Web Service connections are closed upon the completion of any call to a Web method, all state information must be provided within the body of the SOAP document.

Because Web Services are designed to provide course-grained application methods and business-level services, as opposed to the fine-grained object sharing that motivated the development of CORBA and RMI, these limitations are rarely burdensome to a Web Service developer. The simplicity of implementing Web Services is largely the result of designing around the limitations of open standards like HTTP. It would be difficult to argue that the popularity of the Web has suffered because HTTP does not provide a mechanism for maintaining state across invocations!

A Simple Example: GolfCourseService

In Chapters 9 and 10 we wrote and deployed a Web Service called GolfCourseService, which allowed Web clients to find information about golf courses and reserve tee times at specific courses. Here we will review the minimum requirements for a client to invoke this service via HTTP GET and POST methods.

The WSDL document GolfCourse.wsdl provides the method signatures presented by the GolfCourseService Web Service. Here we will invoke the GetCourseDetail( ) method, which presents a query interface into the database of golf courses. A client can retrieve information about a specific golf course by passing in the course's ID.

HTTP GET is the simplest way to invoke a Web Service. A client provides arguments to the Web Service methods as addendums to the Web Service URL. The HTTP GET invocation for the GoldCourse Web Services GetCourseDetail( ) method might look like this:

GET /GolfCourseService/GolfCourseService.amx/GetCourseDetail?id=200
 HTTP/1.1

The Web Service client will return an XML result document in response to this GET request. Likewise, we could perform a POST HTTP request to send HTTP data from a client form. The POST might look like this:

POST /GolfCourseService/GolfCourseService.amx/GetCourseDetail HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: <length>
id=200

The HTTP Post response document, saved here as response.xml, is a simple XML document, identical to the HTTP Get response document. The response is formatted in plain XML without any SOAP references. The header for the document returned is:

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length

The body of the document (again saved in response.xml) as seen in Listing 11-1 is:

Listing 11-1 response.xml: Document returned by our Web Service.

<?xml version="1.0" encoding="utf-8"?>
<GolfCourse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns=
"http://tempuri.org/">
  <id>20</id>
  <name>Wildwood Green</name>
  <description>Wildwood Green</description>
  <price>50.00</price>
  <telephone>919-999-3333</telephone>
  <address>
    <street>157 Strickland Road</street>
    <city>Raleigh</city>
    <state>NC</state>
    <postalCode>27613</postalCode>
    <country>usa</country>
  </address>
  <tees>
     //Tees information omitted for brevity
  </tees>
</GolfCourse>

Platform Issues and Limitations

Because Web Services are based on open, ubiquitous standards like XML and HTTP, platform issues and limitations are kept to a minimum but are not completely eliminated. This section discusses some of the platform-specific issues around Web Services, including how tightly the development environment is integrated with the execution environment and the capabilities of lightweight platforms (such as PalmOS devices).

Windows

Microsoft Windows NT, Windows 2000, and Windows XP provide the most complete platform for developing and deploying Web Services applications. Visual Studio .NET provides a complete development environment for building, deploying, and testing XML Web services. Microsoft IIS provides a Web server for presenting your Web Service to the Internet and managing issues like security. Windows itself provides an application server environment for running your Web Service.

By developing and deploying your Web Service on a Windows operating system, you can be assured that the system conflicts that inevitably arise in mixed development environments will be kept to a minimum. Visual Studio .NET guarantees that your operating system, Web server, Web application server, and development environment are all up-to-date and compatible.

Using Visual Studio .NET you can develop your XML Web service application server using one of several languages, including C, C++, and C#. You can also easily generate Web Service clients for any platform that supports HTML 3.2-capable Web browsers. Visual Studio .NET allows you to create Web forms that invoke appropriate code on the server side, instead of on the client side, to support the platform-independence of the XML Web service client.

UNIX and Linux

The family of UNIX operating systems has a good track record for supporting mission-critical and enterprise-wide application server deployment. UNIX operating systems offer a high degree of reliability and tend to be stable throughout a system's load curve. UNIX systems have also been easier to scale, and more robust in the face of scaling, than other operating systems.

Many developers and organizations find the greater availability of open source applications for UNIX operating systems intriguing. Given the variety of application server choices on UNIX, an organization can choose to spend more money for off-the-shelf commercial solutions or spend less money and more developer resources configuring open source solutions.

The plethora of UNIX configurations and available application server options presents a double-edged sword. Each development environment is likely to present a different combination of a UNIX kernel, Web server, Web application server, development tool set, and other variables. Application developers are more likely to struggle with compatibility issues in a UNIX environment than in a Windows environment. Such environment and compatibility issues tend to make rapid application development on UNIX platforms less feasible than on Windows platforms.

Portable Devices and Handhelds

Handheld devices present some of the most challenging issues faced by distributed-application developers. The portability and widespread deployment of such devices makes them attractive client targets for distributed-application development.

The issues around building distributed applications for these devices are obvious: these devices present limited UIs with constraints on screen size and user input capabilities (that is, these devices rarely present a full keyboard). Handheld devices also present severe constraints in application support, processing power, and available memory, both in RAM and persistent storage.

Network connectivity might be the biggest challenge. Handheld devices, when they are connected to a larger network, normally present low-bandwidth connections of an intermittent nature. The user of such a device must explicitly connect or dial into a network. This requirement might change soon, however, with the advent of portable, high-speed, local area network connections.

The Java 2 Platform, Micro Edition (J2ME technology) allows you to develop Java-based applications on portable and handheld devices, including PalmOS. Using J2ME you can build PalmOS-compatible versions of lightweight XML parsers like MicroStar's Aelfred.

Other Platform Issues: Security, Scalability, and State

Other particularly important issues in designing distributed applications in a heterogeneous network environment include security, scalability, and state. Security generally requires knowledge of each target OS as well as the intermediary transport protocol. Scalability is a classic software design problem, complicated in a distributed environment by the number of variables present (multiple hardware configurations, operating systems, and network). State is a ramification of building services above the stateless HTTP protocol. Here we take a brief look at each.

Security

Once again, Web Services' use of open standards means that we don't have to invent another mechanism for providing secure transmission of data between client and server. Because the Web Services transport protocol is HTTP, the Web Service application looks just like any other Web application running on your Web server. You will continue to use the same mechanisms for authentication, authorization, and encryption that you use with your existing Web server and Web applications.

If we want to provide secure over-the-wire transmission of our Web Services' HTTP requests between client and server, we can use Secure Sockets Layer (SSL), which provides secure HTTP transmissions via public key encryption. To do this we must be sure that our Web server is configured to provide SSL support. For added authentication security we can register X.509 certificates for client identity verification with Verisign.

Because the client accesses Web Services from a URL, just as the client would access a Web page, we can use any of our Web server's security functionality as part of our Web Services' security component. We can require user authentication to access the Web Service URL, or we can restrict access to specific IP addresses or ranges of IP addresses. We can also authenticate Web Service clients and maintain that authentication between HTTP requests by issuing session cookies. All of this functionality is provided by your Web server.

At the Web Services application level we can implement a variety of security mechanisms, including role-based security. Say we offer a Web Service that performs payroll functions. A client with the role of "clerk" might be able to issue paychecks, but not view salary information. A client with the role of "supervisor" might be able to change salaries. The role of the client, along with authentication information to validate that role, can be passed to the Web Service in the incoming SOAP document as arguments to the appropriate method calls.

Scalability

Few scalability issues are specific to Web Services. Developers of Web Service clients should attempt to minimize the required amount of object state information that must be serialized into an XML file and also minimize the size of data objects that must be serialized. Overhead of serializing, un-serializing, and re-serializing object data presents one of the most obvious potential bottlenecks in a distributed Web Service application. Any savings by the client in serialization time is also likely to save un-serialization and re-serialization time on the server.

Because the Web Services server presents its services to the outside world as a URL or set of URLs, the issues surrounding the scaling of Web Services servers is similar to the issues around scaling conventional Web servers. The developer of a Web Service server should minimize object state information required by the service and minimize the size of objects likely to be passed to the service. For large Web Service applications a multitier architecture is feasible, just as one might deploy a multitier architecture for serving conventional Web sites and applications.

State

Because Web Services rely on the stateless HTTP protocol, they must be designed so that all necessary state information is included in the client request for the Web Service. Some work-arounds for HTTP's stateless nature exist, such as using session cookies to maintain authentication state between HTTP invocations. In general, however, the Web application must be designed to work under conditions of a nonpersistent, stateless connection with potentially high latency and (depending on the system environment) unknown or low bandwidth.

Why Not Use Web Services?

Given the ease of implementing Web Services, why would we want to use any other mechanism for creating client/server applications? Despite the many advantages presented by Web Services, they are not the right solution for many situations. Situations where a Web Service solution might not be appropriate include environments in which the following situations are present.

Speed is a Priority

Several areas present possible speed bottlenecks to Web Service applications. Marshaling object data to an XML file on the client can be resource intensive. Distributed applications that maintain an object's native format for over-the-wire transmission have an advantage here.

The same bottleneck appears four times: when the client creates a SOAP XML file for transmission to the Web Service server, when the Web Service server receives the SOAP file and unmarshals the data, when the Web Service server marshals the result of its Web method(s) into a SOAP result file, and when the client unpacks the SOAP result file. The time and computing resources taken up by these operations can be significant, especially if large data structures are passed around. (Because HTTP requires that data be passed by value, all input and result data must be passed over the wire.)

The HTTP protocol presents another potential speed bottleneck. HTTP provides an effective protocol for general Internet traffic. However, lack of control of and access to the underlying transportation mechanism means that the Web Service HTTP requests and responses are at the mercy of current network conditions at all levels (LAN, WAN, and Internet).

State is Important

The stateless nature of Web Services, mandated by the stateless HTTP protocol, is not appropriate for all distributed applications. Applications that must maintain an open database connection, for example, cannot operate effectively in a stateless environment. Applications in which data should be passed by reference instead of by value are not appropriate for Web Services either.

Imagine a Web Service that performs an insert operation on an address field component of a 20-MB data file. A Web Service that provides this capability would need to pass 20 MB of data from the client to the server (after marshaling that data to an XML file). The client would have to receive 20 MB of data, parse the SOAP file, unmarshal the data, perform the insert operation, remarshal the data, and pass the 20-MB SOAP file back to the client, which needs to parse and unpack the result file. All to change a single record! A pass-by-reference mechanism would be more appropriate here.

Through the judicious use of offsets to manage changes to large data files, you can minimize the number of times a large data file is passed back and forth between Web Services clients and servers. Visual Studio .NET provides an abstraction called Data Sets to support working with large XML documents. The Data Set automatically handles update issues when a client application is finished modifying a large data file. Another set of classes called Data Adapters handle all the low-level database interactions. Other APIs for building Web Services will typically provide APIs to perform the same role.

Homogeneous Deployment Environment

Marshaling and unmarshaling your Web method arguments with each Web Service invocation will probably require some overhead. Instead of marshaling your data with every Web Service call, you can save significant computing resources by maintaining objects in the native serialization format of your language and operating system. If you are deploying distributed applications in a homogeneous computing environment, you might want to use more traditional methods for implementing them.

Security Is Critical

Although Web servers can provide a high degree of security with SSL and its underlying public key encryption mechanism, other transport protocols are likely to provide a better solution within corporate firewalls or other closed environments.


Conclusion

In this chapter we looked at historically available mechanisms for implementing distributed applications, from UNIX sockets through Web Services. We've provided enough of an overview of these mechanisms so that you can see the strengths and limitations of each.

Because Web Services are based on open, ubiquitous standards such as HTTP and XML, Web Services can be easily implemented on far more diverse platforms and operating systems than other, previously available mechanisms for creating distributed applications. Even PalmOS devices can generate an HTTP POST request to a Web Service server and receive a result as an XML document! Never before has the bar been so low for a platform to participate as a distributed-application client.

The bar for participating as a Web Service server is nearly as low on the server side. A Web Service server needs only to receive a service request and data, in the form of an HTTP GET or POST method or as a SOAP document, marshal the data into an appropriate format, act on the request via a Web method, and return the result via HTTP as an XML document.

You can create Web methods today on any platform that can communicate via HTTP and process XML files. You can do this from scratch using a low-level programming language, but you might want to use the available support of an IDE for Web Services. Visual Studio .NET handles any cross-platform compatibility issues for you, including configuring client browser or form-based interfaces to run any client-side scripts on the server.

In summary, Web Services present far fewer constraints and limitations than do legacy mechanisms that support cross-platform application development. Any platform that supports HTTP can perform as a Web Service client. You can build Web Service UIs using HTML and JavaScript. It's now easier than ever to prototype, build, test, and deploy distributed cross-platform applications.


Legacy Systems Integration

Today's corporate environment is intensely competitive, and corporate leaders look to their information systems for an edge. Keeping up with technology while protecting the investment in existing applications is a constant challenge for most businesses. Many companies, in fact, most of the largest, do not have brand new systems that store, support, and manage their data. Upgrading to today's systems might not even be an option because of the dependency on old data and the inability to access it if a new system is purchased.

In addition to these challenges, companies are moving rapidly to give employees and customers access to business processes on the Internet in the form of suites of enterprise applications. This openness creates a demand to make the existing mission-critical business systems accessible through the Web.

In this chapter we will explore some of the issues and solutions in legacy systems integration. We will begin with a discussion about the challenges facing most legacy integration projects in an enterprise. We will then describe some common methods for accessing and controlling legacy applications from your new applications. Finally we will describe an approach for legacy systems integration using an XML-based layered architecture. The aim is to show you how XML can help position an organization's legacy systems for the future and tap those vast resources that at one time seemed as hidden away as a pharaoh's tomb.


A Definition of Legacy Systems Integration

Despite all the changes and "paradigm shifts" that have rocked the computing world over the past several decades, global enterprises still store the bulk of their data and process the majority of their transactions on mainframe-based systems such as VAX and S/390. These are tried-and-true systems that form the backbone of a typical organization's business processes and transactions.

These systems are usually developed in COBOL or another obscure language and are built as independent batch-oriented systems using proprietary data storage and communications protocols. The UI is usually complex, heavily loaded, and character-based. Business logic, data access logic, presentation services, and control flow are typically mixed within the confines of individual monolithic programs. Finally, access to the data is often limited and only possible through proprietary means.

You should realize that the term "legacy" is by no means restricted to the mainframe-based systems or other systems that have developed since the onset of Web-based applications. As technologies relentlessly move forward, you will always have systems that were not developed using the latest tools, languages, and protocols. For example, in our Internet-centric world of IP and XML, even relatively recent technologies such as Visual Basic and PC LAN-based client/server systems can be considered legacy systems. Of course, to some companies these solutions are no less valuable than the mainframe-based systems.

In a perfect world companies would rewrite legacy applications to be Web enabled. Such projects, however, can take months or years, so companies are not about to throw out the legacy applications currently running their businesses. Therefore, few projects have the benefit of being able to start from scratch. Instead, most development teams need to take into account existing legacy systems when they're developing new applications. This is known as "legacy systems integration," and a whole industry has grown around it.


Challenges to Integration

No matter which way you approach integration with legacy applications, you're certain to have more pain than fun. The older and less documented the systems are, the harder and more error-prone the integration becomes. The result is a classic "chicken and egg" syndrome, where throwing away the legacy data and functionality will kill the business, but staying with them will do the same. Trying to update the system will probably take longer than starting from scratch, but you can't throw away the old data. You come full circle.

As we talk about these issues we will find that integration problems fall into four main categories: documentation, interfacing, availability, and scalability. Now we will spend some time exploring these in detail.

Documentation

The unfortunate truth about legacy systems is that they're often poorly documented, leaving developers and businesses people alike reluctant to make changes. Everyone is afraid that doing so will break the system in some way. To add to the problem, the original developers or owners of these systems might not be willing to work with a newer team because of such trivial reasons as internal politics.

This reluctance or inability to change legacy systems is one of the primary reasons why integration is considered necessary in the first place. Without adequate documentation or legacy expert help, your integration projects are bound to face a miserable death.

Interfacing

Interfacing refers to the process of talking to and controlling legacy systems from another system. If you're lucky, your legacy systems were designed with integration in mind, as in the IBM Customer Information Control System (CICS), If so, interfacing should be relatively pain-free. This compatibility is hardly typical with many legacy systems, however, and getting an interface to function properly can be a daunting task.

Depending on whether the legacy system in question was designed with extensibility in mind, interfacing can be both time-consuming and expensive. Be sure to weigh the value of the interface against the effort required to accomplish it. You may find that a complete, perfect integration isn't worthwhile, but that a partial integration of only the most critical parts will suffice.

Availability

Web-based applications are generally expected to be available 24 hours a day, 7 days a week. Your legacy systems, however, might not have been developed with this lack of constraints in mind. Mainframe-based systems, as an example, are frequently designed with a periodic maintenance shutdown requirement. What will your application do when legacy systems are unavailable? How will it handle not being able to access the data it needs to function? How should it gracefully alert users of the situation?

These questions need to be answered when building your application, but the list of questions could be much longer when speaking of a specific application. You must know and understand all of the implications of the legacy system's availability, or at least as many as possible, when deciding how to use it. Any system you develop will inherit these restrictions.

Scalability

For many legacy-integration projects scalability might be the single biggest challenge. Most legacy systems are not designed to handle the large volume of traffic and real-time transactions that systems today must face. Legacy systems were often designed to support an elite few who had access to only a handful of machines. Many of these legacy systems were also batch-oriented and will not be able to handle online queries and transactions. For many applications, especially Web-based ones, being able to handle a large and unpredictable volume of online transactions is a requirement. Users of these applications will not wait days, hours, or even minutes for the systems to response. Including legacy resources in your chain of applications increases transaction time, thereby decreasing scalability.

These are some of the problems that will prevent a legacy system from scaling, and enhancing or improving one of these systems to function in today's environment is hard.


Creating Interfaces to Legacy Systems

We have now covered some of the common problems with supporting and interfacing legacy systems in an enterprise. Because of the wide variety of legacy systems, no single solution can make them all work with newer technology. Some systems provide native functionalities for third-party access, while others support only proprietary features and functionality. Because of this variety, interfacing with legacy systems can be extremely challenging, but it can be done.

An Example Scenario

IBM created CICS Web gateways that allow you to create CGI scripts to make calls into CICS directly, which can greatly simplify the task of interfacing to legacy systems. IBM is one example of a company adding enhancements to its legacy products. These are often products that would have become extinct if not for the enhancements, which have potentially saved their customers millions by allowing them to postpone migrations.

You can interface with legacy systems using five common approaches. They are generalized to represent areas that developers and managers can focus on when trying to utilize old systems for new work. They are

  • Data-level
  • Process-level
  • API-level
  • UI-level
  • Middleware

Let's take a closer look at each of these approaches.

Data-Level Interfacing

The first type of interfacing focuses on accessing legacy databases or files directly at the data level, bypassing most, if not all, of the functionality present in the surrounding application.

If your data is stored in SQL databases you can access them directly using appropriate SQL queries. Some RDBM systems, such as older versions of Novell Btrieve, provide non-SQL, but nonetheless fairly complete, API access to their databases. Other systems provide proprietary data storage mechanisms and often have data import and export functionalities designed specifically for interfacing. Communications with these systems are performed through the exchange of files of a specific format. These usually involve some form of delimited text files.

Some vendors, such as Oracle and Microsoft, provide native XML-based access to data stored in their databases. For example, back in Chapter 4 we discussed some of the built-in features provided by Microsoft SQL Server 2000 and Oracle 9i for exposing and updating relational data as XML. This appears to be the next-generation method for enabling integration.

The main problem with data-level interfacing is that it increases data coupling between applications, thereby increasing your maintenance burden. You might also not be able to access important data validation and other critical business rules that are inherent in the application but not in the method of accessing the information. Finally cost can also be a limiting factor, especially if you need to employ a middleware product.

Process-Level Interfacing

Solutions written for some mainframe environments, especially those written in COBOL, were developed as a series of separately executable programs. Interfacing with these systems usually requires preparing the invocation environment, calling the required program(s), and fetching and processing the returned data. While this formula for process-level interfacing provides a common method of getting to the data, it can be overwhelming when trying to extract only simple information.

CICS, which is a good example of this method, provides an External Call Interface (ECI) to allow non-CICS clients to invoke and control programs under CICS. ECI includes support for CICS security and transactions and, unlike RPC environments, ECI does not require stubs and the use of an Interface Definition Language (IDL) compiler and is therefore easy to use.

Here is an example of how process-level interfacing works with CICS. To run a CICS program using ECI, you must supply CICS with all the relevant information for the invocation, such as the name of the program to invoke and a username and password. You pass data to the CICS programs via a block of data called the COMMAREA block. When the invoked CICS program finishes it places the results in the COMMAREA block.

API-Level Interfacing

Another type of interfacing with legacy systems is to provide an API to access the functionality of an application. Many packages, such as SAP and PeopleSoft, include C/C++ or even COM-based APIs for accessing their systems. However, APIs might be limited in scope, preventing access to the functionalities you need. They might, for example, behave in a fashion you do not expect, such as not being thread-safe.

If your team has the required skill sets, these APIs can provide a powerful means of interfacing to the legacy systems. However, these APIs might be limited in scope, preventing you from accessing the functionalities you need. They might also not behave in the fashion you require, such as (for example, not being thread-safe).

User Interface-Level Interfacing

Accessing legacy applications through UIs, a process called screen scraping, refers to the interaction with the legacy software performed by using simulated user keystrokes and entered data by processing captured screen outputs. This technique is most often used with old "green" terminal-based systems and is one of the last methods a person might use to integrate with a system.

Recently this technique has been used by Web-based aggregation sites for presenting financial data or other types of information. These sites build their contents by combining data extracted from parsing HTML pages retrieved from other Web sites.

Middleware

Some mainframe vendors and third-party companies provide solutions that allow applications to access data through middleware products. These products sit between your new and legacy systems, hiding the complex details of legacy interfacing from you.

Different middleware products can vary greatly in functionality. Some products simply provide a mechanism for data to get from place to place, such as RPC, messaging systems, and distributed object systems. More sophisticated middleware offerings, however, can help manage application logic and resources. The most robust tools directly support significant application functionalities such as credit card transactions for e-commerce. By employing sophisticated caching and asynchronous data-transfer technologies, these high-end products can also provide scalable access to your legacy data. IBM's DB2 WWW Connection and the Sybase Enterprise CONNECT middleware family of products are some examples.

If support for your particular legacy system is available, using the right middleware product can greatly simplify the task of legacy integration. The major downside is that these products, especially the high-end ones, are not cheap. They also require extra hardware resources and administrative care that adds to the cost. You will have to decide whether using a middleware product is cost effective for your particular integration needs.


An Architecture for Legacy Systems Integration

Much time and effort is required for a successful systems integration no matter which methods are available to you. Though integration can be difficult, data is often so valuable that you have no choice but to interact with a system, even if to extract only the data for a migration. Therefore, as you design a method of accessing data in these systems, you should attempt to do so with a method that will remain relevant and capable of supporting an organization's future needs. In this section we will show you an architecture that can be used to effectively implement and maintain legacy systems integration.

Core Criteria

Three criteria should be used to prepare legacy systems for integration. By making the system accessible through multiple communications mechanisms in advance, obsolescence by technology can be minimized. The following list outlines these three areas:

  1. The application should be capable of supporting multiple communications protocols such as HTTP, IIOP, SOAP, and others. While the first and immediate implementation can support only one, hooks should be built in to allow for future extension. The only exception is when you are building a one-time integration to migrate the data into a new system.
  2. The messaging interface should be protocol-independent and consistent.
  3. The data model used by the formatted messages should be defined in terms of the organization's business transactions.

While the first item simply provides for a flexible means to use any integration, the second criterion guarantees that applications can send and receive the same type of messages, formatted in the same way. Such flexibility is irrespective of the underlying communications mechanisms used and demonstrates why XML is perfect for this job. XML is preferred because it is platform-independent and is easy to work with due to the availability of a large number of tools for parsing, transforming, and processing XML documents.

The reason behind the third criterion is that any application already in a system is there to support a business process, a need, or both. Therefore, the interface to the integrated application should be defined using the terminology and artifacts of that process. By adhering to the third criterion the organization shields itself from the eventual replacement of the system as time goes on. In essence it prevents the system from becoming a "legacy system."

The Layered Approach

A legacy application can be prepared for integration through the implementation of a layered architecture pattern that allows for maximum flexibility. This pattern includes

  • A Transport Layer-The outermost layer is responsible for the receipt and return of XML-formatted messages through any given communications mechanism.
  • A Services Layer-The middle layer provides the functionality for interpreting XML-formatted requests, invoking the appropriate operations upon the legacy system, and formatting the responses into XML.
  • A Legacy Adapter Layer-The innermost layer is responsible for interfacing to the legacy system and represents the model of the data.

Regardless of the client type or communications mechanism employed, this architecture allows systems to perform business domain-level services via an XML-based interface. Imposing this method on top of a legacy system will enable the use of that system for years to come, assuming performance is not an issue.

Transport Layer

The Transport Layer, as we mentioned, allows clients of these legacy applications to use various protocols and mechanisms, such as HTTP, CORBA (IIOP), or DCOM, to invoke the services defined by the interface to the application. Classes in this layer can be fairly straightforward because their primary role is to provide a communications bridge to the actual services. The main responsibility of this layer is to make the rest of the system protocol-independent.

Listing 12-1, transport_layer.aspx, is a sample implementation of a Transport Layer that uses the HTTP protocol for invoking the services exposed by the ServiceLayer object defined in service_layer.cs.

Listing 12-1 transport_layer.aspx: Sample Transport Layer for HTTP protocol.

<%@ import namespace="System.Xml"%>
<script language="C#" runat="server">
  /* string used to hold string representation of
     our service request XML message */
  String xmlStr = "<ServiceRequest>";
  /* get names of all form variables into local array */
  String names[] = Request.Form.AllKeys;
  /* loop through form variable to build request XML message */
  foreach ( name in names )
  {
    /* if form variable is called "service", use its value to
       build the ServiceName element */
    if ( name == "service" )
    {
      xmlStr += "<ServiceName>" + name + "</ServiceName>";
    }
    /* othewise use this form variable's name and value to
       build a <Param> element */
    else
    {
      xmlStr += "<Param name=\"" + name + "\">" +
                Request.Form[ name ] + "</Param>";
    }
  }
  xmlStr += "</ServiceRequest>"
  /* create XmlDocument using the reqXML string */
  XmlDocument xmlDoc = new XmlDocument();
  xmlDoc.LoadXml( xmlStr );
  /* instantiate our ServiceLayer object */
  ServiceLayer service = new ServiceLayer();
  /* invoke the perform() method with our request XML message */
  XmlDocument retXmlDoc = service.perform( xmlDoc );
  /* output the response XML message to the client tell
     browser/client that we are outputting XML */
  Response.ContextType = "text/xml"
  /* output the response XML message */
  Response.Write( retXmlDoc.outerXml );
</script>

In this implementation of a Transport Layer, we first built our request XML message from HTTP POST variables. We then instantiated an instance of the ServiceLayer object developed in service_layer.cs and invoked its perform( ) method with the request XML message as its parameter. Finally all that was left was to display the resultant response XML message to the client.

Services Layer

The second layer defines how the services in the system are invoked and how the data returned by the services is formatted. XML-formatted requests are forwarded to this layer via the Transport Layer, and the classes in this layer parse the transaction requests and invoke the appropriate operations on the legacy system.

The methods exposed by the Legacy Adapter Layer and the parameters used in invoking those methods should be used to design the format of the XML messages used by this layer. When designing the format of these messages, keep it consistent across the different services to be invoked. For example, Listing 12-2, sample_request.xml, shows a sample XML message that can be used to invoke the services exposed by the LegacyLayer object defined in legacy_layer.cs.

The ServiceName element is used to indicate the legacy service to invoke. The <Param> elements hold the names and values of the parameters that are used when invoking a particular service; thus the same format can be used to invoke services of different calling conventions.

Listing 12-2 sample_request.xml: Sample service request XML message.

<?xml version="1.0"?>
<ServiceRequest>
  <ServiceName>Order</ServiceName>
  <Param name="customerID">foobar</Param>
  <Param name="itemID">7843221</Param>
  <Param name="qty">5</Param>
</ServiceRequest>

Listing 12-3, service_layer.cs, is an example of the implementation of a Service Layer that uses the XML message defined in sample_request.xml to invoke legacy services.

Listing 12-3 service_layer.cs: Sample Service Layer.

using System.Xml;
public class ServiceLayer
{
  public XmlDocument perform( XmlDocument params )
  {
    /* our Legacy Layer object */
    Legacy legacy = new Legacy();
    /* retrieve name of the legacy service to invoke */
    DocumentNavigator nav = new DocumentNavigator( params );
    nav.Select( "//ServiceRequest/ServiceName" );
    nav.MoveToNextSelected();
    serviceName = nav.innerText;
    /* string used to hold result of the method invocation */
    string res = "";
switch ( serviceName )
{
      case "Order" :
        /* retrieve the customerID, itemID, and qty parameters
           from the Params XML document */
        nav.Select( "//ServiceRequest/Param[@name='customerID']" );
        nav.MoveToNextSelected();
        string customerID = nav.innerText;
        nav.Select( "//ServiceRequest/Param[@name='itemID']" );
        nav.MoveToNextSelected();
        string itemID = nav.innerText;
        nav.Select( "//ServiceRequest/Param[@name='qty']" );
        nav.MoveToNextSelected();
        string qty = nav.innerText;
        /* invoke the orderItem() method in our Legacy object
           to place an order and build result XML document based on
           the outcome. */
        try
        {
          string tranID = legacy.orderItem( customerID, itemID, qty );
          res = "<TransactionID>" + tranID + </TransactionID>";
        }
        catch ( Exception e )
        {
          res = "<Fault>" + e.Message + </Fault>";
        }
        break;
      case "History" :
        /* retrieve the customerID parameter from the Params
           in the XML document */
        nav.Select( "//ServiceRequest/Param[@name='customerID']" );
        nav.MoveToNextSelected();
        string customerID = nav.innerText;
        /* invoke the getOrderHistory() method in our Legacy
           object and output result in our resultDoc XML document */
        try
        {
          string[] tranIDs = legacy.getHistory( customerID );
          /* get list of transaction IDs of past transaction
             and build result XML document */
          foreach ( string tranID in tranIDs )
          {
            res = res + "<TransactionID>" + tranID +
                        "</TransactionID>";
          }
        }
        catch ( Exception e )
        {
          res = "<Fault>" + e.Message + </Fault>";
        }
        break;
    }
    /* build and return result XML document */
    XmlDocument resultDoc = new XmlDocument();
    resultDoc.loadXml( "<ServiceRequestResponse>" + res +
                       "</ServiceRequestResponse>" );
    return ( resultDoc );
  }
}

The ServiceLayer object exposes a single method called perform( ) that accepts an XmlDocument argument which is used to determine the name of the legacy service to invoke and their input parameters. The perform( ) method returns an XmlDocument object containing the results of this invocation.

ServiceLayer supports two services, the Order that is used to place an order and the History that is used to determine the transaction history of a specific customer. Note that the implementation of these services are handled by the respective orderItem( ) and getOrderHistory( ) methods in the LegacyLayer object in legacy_layer.cs that we developed in the last section.

Legacy Adapter Layer

The innermost layer contains all the code necessary to interact with the legacy system. Using one of the methods outlined in the "Creating Interfaces to Legacy Systems" section is suggested. Business-level behaviors and rules encapsulated by the legacy system are exposed to the Services Layer, so this layer does not need to know anything about XML.

When designing services for this layer you need to have the correct granularity. In building distributed systems, designers commonly specify interfaces that are too finely grained. These designs do not scale well, and clearly defining and managing transactional boundaries is difficult. For example legacy_layer.cs, shown in Listing 12-4, contains the pseudocode of a LegacyLayer object that exposes two methods for placing orders and inquiring about previous transaction history.

Listing 12-4 legacy_layer.cs: The psuedocode of a sample Legacy Layer.

public class LegacyLayer
{
  /* place an order return the new transaction ID or throw
     an exception if an error occurred. */
  public string orderItem( string customerID, string itemID, string qty )
  {
    /* insert your actual implementation here... */
  }
  /* get order transaction of a specific customer. return a
     string array containing the list of the transaction IDs
     of previous order transaction or throw an exception if
     an error occurred. */
  public string[] getOrderHistory( string customerID )
  {
      /* insert your actual implementation here... */
  }
}
}

Note that these methods deal with native data types directly instead of XML messages. Also note that the granularity and stateless nature of the methods can simplify transaction management as well as improve scalability.

Availability and Scalability Considerations

One common approach for tackling the availability and scalability issues we discussed that are associated with integrating legacy systems is to decouple the legacy systems from the new application by using a messaging server such as the MSMQ or IBM's MQSeries. Using a messaging server allows the two separate subsystems to operate at their own pace.

On the application side you can concurrently send thousands of requests and obtain excellent performance from a messaging server. You can process messages at a pace the legacy system can handle, and therefore, can maximize both systems without overloading either. For example, if only five connections are available through IBM Systems Network Architecture (SNA), you can process five messages at a time. This allows you to throttle the load on the legacy system while at the same time addressing your availability concerns and allowing your application to scale appropriately.


Conclusion

Legacy systems integration is a huge and complicated topic that merits a whole book of its own. In this chapter we've merely scratched the surface, but we've given you an overview of the more common issues surrounding legacy integration. Some of the issues stemmed from the fact that most legacy systems simply were not designed for the kind of availability and scalability demanded by today's applications. We also briefly discussed the different ways in which applications can interact with a legacy system, and we identified a few methods, including data-level, process-level, and API-level interfacing, and discussed their characteristics and problems. Finally we provided a description of an XML-based, layered architecture that can be used to implement extensible legacy integration solutions.


Cross Device Development

In this book we have looked at ways to use XML to aid in Web applications development. So far discussion on application clients was limited to a Web browser (Microsoft Internet Explorer 5.5 more often than not). The explosion of the wireless industry and our newfound ability to plug almost anything into a network, from TVs to toaster ovens, has developers scurrying to Figure out how to port their products to alternative devices. Rest assured, XML is making its way to all of these devices.

Some of the anxiety over how to support all of these small devices is overkill. How often do you need to check the performance of your 401K on your microwave while preparing dinner? Despite this, there are legitimate reasons to support cell phones, PDAs, and other devices with your application. The next killer wireless application is out there, and you just need to find it. It would be great, though, if you didn't need to rearchitect your entire product suite along the way.

Fortunately for you, the developers, (and us, the XML authors) XML offers a number of features that make cross-device development and support easier than ever. This chapter will look at some emerging standards in cross-device development, how these standards use XML-based technologies, and how they are incorporated into the latest edition of Microsoft's .NET Developer Studio. We will discuss the benefits and limitations of these devices, and we will apply what we have learned to the Golf Reservation System sample application built in the previous chapters.


Applications for the Web and Beyond

In today's industry we need to design our applications with extensibility in mind. New devices, technologies, and specifications are emerging so rapidly that understanding what they all mean, let alone incorporating them into our products, is difficult. We must design the next generation of applications to be receptive to change. We need to be able to plug new interfaces and functionality into a working application quickly and with as little redesign as possible.

Luckily, the sound programming design concepts we learned in school and in the field are still valid. Object encapsulation and loose coupling between application layers will make any application more extensible. When designing a layer of an application, focusing on the what (that is, what the logic is supposed to do) and how (how the logic performs its task) instead of on the who (who or what kind of client can use the logic) will open your mind to new possibilities.

If you partition your application logic by hiding it behind well-defined interfaces, sooner or later you will find new and different ways to use those interfaces. Try not to hardwire different application layers together for expediency's sake. This approach rarely saves you any time and, much worse, it closes countless integration opportunities. In this section we will use the concept of partitioning presentation and content to create a new breed of wireless applications.

The Separation of Church (Presentation) and State (Data)

XML is a handy tool for partitioning the different regions of your application. It is an instant interface. You can use schemas to describe your interface and XML to implement it. With specific regard to client applications, you should use XML to separate data from presentation logic. Combining the two restricts using the data to a specific subset of devices. By separating presentation from content you open up a myriad of ways to use information (many of which probably haven't been invented yet) in your application.

You should create a schema to define the data that flows through the front end of your application. Some clients, such as PC-based Web browsers, will be able to present all the information to the user at once. Other clients, such as cell phones and PDAs, can only digest small bits of information at a time. Despite disparities in ability among different types of devices, you can use the same XML content to drive all types of clients. You might have to plug in one or more XSLTs to massage the data into a format that a particular client device can absorb, but these transformations can be applied after the content is developed. In other words, the layer of your application responsible for generating content does not need to know what types of consumer devices will be on the receiving end. The relationships between these layers can be seen in Figure 13-1.

Figure 13-1 Different layers of a Web application.

Application Design Concepts

The fundamental hurdle that makes cross-device programming a challenge is that no two devices behave the same way. If you thought building and maintaining your Web site to support two major browsers was problematic, imagine how difficult supporting different types of cell phones and PDAs could be.

Not All Devices Are Created Equal

Classes of devices all have different strengths and weaknesses. For example, PCs are powerful and offer limitless UI possibilities. Users can communicate with an application by typing on a Microsoft natural keyboard, by pointing and clicking the mouse, by touching the screen, or even by talking into a microphone. PCs are ideal for traditional computing tasks where a user is seated in his or her chair and is ready for serious work or play.

Televisions have large, low-resolution displays suitable for showing big pictures to many people. Their low resolutions and low refresh rates would make them unsuitable for use as a word processor. You might, however, want to view programming schedules, play games, manipulate a video, and check your e-mail on the household tube.

Cell phones have limited and cumbersome I/O devices. They have tiny screens, even tinier keyboards, and no real mouse. Still, they are portable, convenient, and have the unique quality of being able to announce their presence to everyone around whenever something important happens. The point is that all devices perform some functions well and others not so well.

Partitioning Content and Presentation Layers

Saying "Just separate presentation from content and your cross-device problem will be solved" is easy, but development is not that simple. Like it or not, you will spend a considerable amount of time redesigning your UI whenever you begin development for a new client device. An application's browser-based UI that looks fantastic on a 17-inch monitor probably won't look as hot or be as user friendly (or work at all) on a 2.8-cm-by-3.75-cm display.

That example demonstrates a serious reduction in screen real estate. In the case of small, portable devices you need to think about what information is critical to each screen in your application and what information is expendable. Generally speaking, you need to Figure out how to overcome the obvious obstacles many of these devices present and, better yet, come up with ways to use their unique features to add value to your application.

A computer program cannot solve these problems for you. Building a cell phone, PDA, or TV client requires time and brain cells. The trick is doing as much as possible in the presentation layer so as to preserve much of the work you have already done.

Your decision on how to partition the content and presentation layers of your application will have significant impact on its cross-device portability. If your content layer is tied too closely to a particular presentation schema, you will have a tough time adapting it to a radically different UI environment. You might need to reengineer in the best case, or build a device-specific content layer in the worst case.

Here are two different examples of XML content documents that might exist within a human resources application. Both documents are identical in terms of the pieces of information they convey, but are much different in the focus of their metadata. Afterwards we present a compromise between the two.

Presentation-Based Content Documents

The first XML document, PersonDocument.xml, uses a presentation-based schema. The metadata in this type of document is used to convey hints about layout and document structure. It does not contain much information about the type of data being shown or the context in which it's being used. The focus of the presentation layer using this approach is adding formatting, layout, and stylization specifics to the document skeleton in the content document.

This approach eases the burden of the presentation specialist. All he or she needs to do is create an XSLT template for each class of document that can appear in the application. A half-dozen major formatting templates might be enough to support hundreds of different content documents. Moreover, because these rules apply to document classes rather than specific pages, the application is guaranteed a uniform look and feel. The look and feel can also be changed at a global level with little effort.

Although the presentation layer can significantly alter the formatting of the resulting document, it does not have much control over what is shown. The source content document does not have enough metadata to enable the presentation rules to make judgment calls over the relative importance of its data. For example, the Picture field could be omitted from the result document for display on a small device without losing much. However, nothing in the source document tells the presentation layer this.

Another shortcoming inherent in this approach is that the document structure the content XML document suggests might not be ideal for all client devices. For example, a single block of content designed for a PC Web browser might need to be broken down into a number of WML cards. Listing 13-1 shows a presentation-based XML document describing a person.

Listing 13-1 PersonDocument.xml: This XML content that uses a presentation-based schema.

<content>
  <header>
    <title>Person Detail</title>
  </header>
  <section>
    <title>John Smith Employee Information</title>
    <view>
      <properties>
       <property description="First Name">John</property>
       <property description="Last Name">Smith</property>
        <property description="Address">200 Brattle Street</property>
        <property description="City, State Zip">Cambridge, MA 02138
</property>
        <property description="Hair Color">brown</property>
        <property description="Birth Date">11/30/1974</property>
        <property description="Favorite Color">blue</property>
        <property description="Picture"><img src="images/jsmith.jpg"/>
</property>
      </properties>
    </view>
  </section>
</content>

Object-Based Content Documents

The second XML content example, shown later in the personobject.xml file, follows an object-based schema. The metadata in this class of document is not concerned with conveying any display information whatsoever. Instead, the metadata expresses the type and context surrounding the information it contains. This takes some work off the content developer's plate by limiting his or her responsibilities to querying any data repositories for information related to the page context and reformatting that data into XML.

The presentation designer now takes the object-based content document and builds the entire application page. In an extreme case the presentation designer must build a unique set of transformation rules for each view in the application. This can lead to a vast library of transformation rules that are difficult to maintain. Applying global stylization policies beyond simple CSSs is also difficult because each view has a unique transformation.

Despite these negatives, object-based XML content documents are more reusable than their presentation-based counterparts because they do not imply any UI decisions. The presentation developer can extract as much data from the content document as he or she needs to get the job done. All layout, formatting, and stylization decisions fall under the presentation domain. The presentation developer has complete freedom, yet complete responsibility, in choosing how to craft the UI experience for a particular device. Listing 13-2 shows an object-based approach to the XML document found in Listing 13-1.

Listing 13-2 PersonObject.xml: An object-based XML document.

<content type="person" view="detail">
  <person>
    <firstName>John</firstName>
    <lastName>Smith</lastName>
    <address type="home">
      <address1>200 Brattle Street</address1>
      <city>Cambridge</city>
      <state>MA</state>
      <zip>02138</zip>
    </address>
    <hairColor>brown</hairColor>
    <birthDate>11/30/1974</birthDate>
    <favoriteColor>blue</favoriteColor>
  </person>
</content>

In this scenario most of the hard presentation work is handled in the presentation layer of the application. Device-specific presentation rules must be addressed somewhere in your application. Where this logic fits best, either in the content layer or in the presentation layer, depends mostly on the nature of your application and staff expertise.

A Compromise

We have explored some of the positives and negatives associated with presentation-based and object-based content models in cross-device design. You are probably wondering if it's possible to blend the two to create an ideal content schema that is flexible enough to extend to new devices but strong enough to support broad document classes.

One approach to this problem is to use Content Adapters to marshal data from object-based XML into device-specific presentation-based XML. You can then apply a small set of device-specific formatting templates that apply layout and stylization rules to the generic document templates produced by the content adapter. Figure 13-2 shows how these formatters fit into the larger application.

The Content Adapter approach has the following benefits:

  • It cleanly encapsulates the steps of content generation, document production, and formatting. XML schemas can be used to define the interfaces at each step, allowing you to divide the work across different people and skill sets.
  • It's extensible. Content Adapters and Formatters can be built for new device requirements as they arise. Changes to one device's formatter will not affect the behavior of any other supported device.
  • The device-specific formatting layer guarantees look-and-feel consistency among document classes in the application. Global style changes can be implemented by modifying templates in this layer.
Figure 13-2 The formatting chain from XML source content to device-specific presentation.
The Apache Group's Cocoon project touches on this design pattern. For more information visit http://xml.apache.org/cocoon2/index.html .

The Challenges of Developing for Small Devices

One pitfall you might encounter while developing for new devices is making the assumption that related devices behave the same way. The browser wars taught us that, no matter how precisely a specification is written, there will always be different ways to interpret it. The challenge of supporting an ever-expanding list of cell phones and other devices makes the Internet Explorer vs. Netscape Navigator problem look trivial.

For example, the list of different types of Wireless Application Protocol (WAP)-compliant cell phones on the market appears to grow daily. These cell phones, though similar in terms of supported standards, differ in many ways. Variations in screen sizes, component rendering, and auxiliary features can all affect the way you design a client application for them. Therefore, it is critical that you know as much as possible about the type of device and browser that is connecting to your Web application.

Device Detection

Detecting these differences can be tricky. The traditional method of browser detection, querying the HTTP_USER_AGENT CGI variable, becomes tedious and error-prone as the number of potential client devices grows over time. This method of detection requires the UI designer to have a priori knowledge of the capabilities of specific devices at design time. Browser detection logic inflates the amount of code the presentation developer needs to write and makes it needlessly troublesome to maintain.

Fortunately, you don't have to write all of this code yourself. A number of software companies offer tools to help write targeted code. Air2Web.com, Gadgetspace.com, BeTrend.com, and 2Roam.com all provide wireless solutions to developers. In June 2001 Microsoft released version 3 of its Mobile Explorer Toolkit. This toolkit, available for free on Microsoft's Web site, enables developers to create client applications for HTML- and WAP-enabled mobile devices. The toolkit integrates natively with the .NET Developer Studio. We will cover the Mobile Explorer Toolkit in greater detail later in the chapter.

Composite Capabilities/Preference Profiles

The W3C is already taking steps to remedy the device/browser detection problem. A working draft of the Composite Capabilities/Preference Profiles (CC/PP) specification was released in July 2000. The goal of this specification is to provide a means for client devices to express their capabilities and preferences to content servers and shifts the burden of identification to the client. This approach supplies application developers with better information at request time to determine what type of document should be returned to the client.

According to the specification, client devices will use the RDF to express their capabilities via a standardized set of CC/PP attributes. The CC/PP spec defines a core feature vocabulary that can be used to categorize a wide range of user agents. This vocabulary includes attributes to describe, among other factors, the client's document encoding, media format. (For example, GIF, JPG, MPEG), and printing capabilities. HTTP requests will include an embedded CC/PP RDF document the application server can use to determine how best to handle the request (for example, should a particular image type be suppressed? Does the client support MPEG video? What version of WML does the client support?)

If you would like more information about CC/PP, see http://www.w3.org/Mobile/CCPP .

Related Specifications

Any class of device you might target in your development efforts probably has a set of associated specifications that should be followed at all costs. Most of these specifications rely on XML to exchange data to and from the device. Ironically, the most often-used specification, HTML, is the loosest in terms of XML conformance. The move to XHTML should tidy up the language considerably.

The Wireless Application Protocol

The Wireless Application Protocol (WAP) and its related markup language, Wireless Markup Language (WML), are arguably the most exciting cross-device specifications available today. The explosion of the wireless industry has CEOs of software companies everywhere begging developers for mobile solutions in their products. The WAP specification was developed by the WAP Forum ( http://www.wapforum.org), an association with over 500 members from some of the most influential companies in the wireless industry.

The WAP specification is very broad. It defines the entire network operating environment for wireless devices. This includes session protocols, transaction protocols, transport layer security, datagram protocols, gateway agents, WML, and WML Script. The last two pieces of the specification are of greatest interest to the client application developer. Most WAP run-time environments (such as .NET) will take care of the rest of the plumbing for you. All you need to worry about is building your WML client.

The WML looks like HTML. WML is based on XML, so it uses a tag-based syntax that should be familiar to anyone who has ever written a Web page. WML documents are similar in structure to HTML documents. They are divided into a header and a body. The header contains meta-information about the page. The body contains all the information that will be presented in the page. Page content can consist of plain text, images, tables, paragraphs, and user input controls.

WML also has an associated scripting language called WMLScript. It's loosely based on the ECMAScript scripting language commonly found in Web pages. WMLScript is specifically geared to wireless devices. It can validate user input, access device-specific functionality (for example, a script can be programmed to dial a certain number), and generate local messages and dialog boxes. Like HTML, WML also provides an event mechanism to allow programmers to insert WMLScript event handlers.

Unlike HTML, WML introduces the concepts of decks and cards. A single WML page request downloads a deck, which might contain one or more related cards, or pages. The user can flip between these cards without making expensive round trips to the server. Cards can access other cards' data within the same deck. The reason for having decks and cards in WML is because of the severe input restrictions intrinsic to small mobile devices. A single HTML form might need to be converted into multiple tiny WML forms just to capture and display the same information. Decks provide a means to bundle associated pages into a single client request. The Listing 13-3 file here shows a simple WML document.

Figure 13-3 shows how this file looks on the Nokia Developer's Kit after it is compiled and loaded.

Listing 13-3 HelloWorld.wml: The obligatory "HelloWorld Introduction."

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
  <card id="card1">
    <p>
      <do type="accept">
        <go href="#card2"/>
      </do>
      Hello World!
      This is the first card...
    </p>
  </card>
  <card id="card2">
    <p>
      This is the second card.
      Goodbye.
    </p>
  </card>
</wml>
Figure 13-3 The HelloWorld.wml example shown on the Nokia developer's kit.

WML and WMLScript are not difficult to learn for experienced Web developers. Again, the trick is not knowing how to build mobile clients for your application, but designing your application to get the most reuse out of your logic when it comes time to bring your mobile solutions to market. This planning will give you a leg up on the competition.

VoiceXML

The VoiceXML specification was designed to make Web content available over traditional spoken phone lines. Users can manipulate VoiceXML client applications through spoken dialog and by pressing options on a touch-tone keypad. Content is sent to the user via prerecorded phrases, synthetic voice, and music. VoiceXML can make your enterprise Web Services instantly accessible to anyone who can reach a telephone.

Although some might view traditional voice-driven interfaces as "less sexy" than wireless Web browsers, sometimes voice-driven interfaces are more appropriate. Take the common example of checking the arrival time of an airline flight. Dialing a phone number and saying the words, "American Airlines; November 1, 2001; Flight 349" is much easier than fumbling with a tiny Web browser and slow Internet connection. The VoiceXML standard replaces proprietary call center technologies. Developers can use XML to specify spoken grammars that are tuned for use in their applications.

TVWeb

The TVWeb initiative was begun to integrate television and Web technologies. This project is still in its beginning stages and no official specifications have been released yet by the W3C. The working group was formed in response to a June 1998 W3C workshop on "Television and the Web." The group formed to establish global standards to govern the application of Web technologies, such as HTML, CSS, and XML, to the television.

The first two deliverables of the TVWeb project will be to adapt the HTML and CSS specifications to the television. These languages will be paired down in some areas and extended in others to make it well suited to TV. URL formats will also need to be changed to address TV content. Other topics to be considered by the group include authoring guidelines, TV metadata, device profiling, timing control and synchronization, and VCR controls. Look for standards to emerge from this group in the coming years.


Extending the Golf Reservation System Application to Wireless Devices

In this section we will extend the Golf Reservation System application client from Chapter 9 to the wireless realm. The Golf Reservation System client interfaces a large database of golf courses and tee times via Web Services. Users will be able to browse local courses and reserve tee times. We will look at design requirements from the project and discuss two implementation strategies. The first strategy will use the Mobile Explorer Developer's Toolkit. The second approach will extend the Golf Reservation System client XSLT skins library to add a WML front-end to the application.

Application Requirements

The wireless front-end to the Golf Reservation System application will include the following features. (Please note that this is not a production system and should be used only for illustrative purposes.)

  • Users can search for golf courses against the same criteria available in the PC browser interface (name, city, state, zip code, and country).
  • Users can review the results of their selection in a list format.
  • Users can link to a specific golf course and review information about that course. This information will include the course's name, address, and price.
  • Users will be able to request a tee time on a specific date.
  • The mobile client will display all available tee times for the date chosen by the user.
  • The user will be able to select an open tee time and reserve the spot.
  • The mobile client will report the success or failure of the operation to the user. If the reservation is approved, the client will thank the user and display detailed information about the approved tee time.

Using the Mobile Explorer Developer's Toolkit

We will use the Mobile Explorer Developer's Toolkit to take a first crack at implementing our Golf Reservation System mobile client. Installing the toolkit will immediately update the .NET Developer Studio with two new features.

The toolkit is available at Microsoft's MSDN Web site at http://www.microsoft.com/mobile/phones/default.asp .

The first feature is the ability to add Mobile Web Forms to your project. Mobile Web Forms are similar to regular Web forms in structure and design. The Mobile Web Form wizard creates two new files for you: the .aspx file that contains the display logic and the .aspx.cs file that contains the super class definition that the Web forms page extends. The class definition declares protected properties that are available for use in the .aspx page and contains all server-side event handling subroutines.

The second key feature of the Mobile Explorer Developer's Toolkit is its cell phone emulator. You can use the emulator to test your programs without the need for an expensive phone. The emulator, which can emulate two styles of phone, makes iterative development and testing a snap.

Upon creating a new Mobile Web Form, the .NET Developer Studio presents you with a palette of controls suitable for use on WAP clients. These include simple form-input controls such as text boxes and selection lists. The wireless control palette also provides more advanced controls such as expression validators and a calendar control. The calendar control is particularly useful for creating a cross-device interactive date chooser.

In this example we will use the Mobile Explorer Toolkit to implement the first three functions in our requirements list: the search, search results, and course detail pages. We will create a single Mobile Web Form that contains three separate forms (one for each view). The Mobile Toolkit will handle breaking the forms down into individual WML decks and cards that will be retrieved by the WAP client. The Mobile Toolkit will also handle the creation and maintenance of the server-side session state by using URL session identifiers.

Dragging a Mobile Toolkit control from the palette onto a form adds a corresponding XML element from the mobile namespace to the .aspx page source. For example, these two lines of source code appear in the search.aspx page:

<mobile:Label id=Label1 runat="server">Course Name</mobile:Label>
<mobile:TextBox id=GCName runat="server"></mobile:TextBox>

The first line adds a label to the form with the description Course Name. The second line creates an adjoining text input control with the id GCName. The value inserted into this control can be submitted to the server or referenced by WMLScript within the page.

Why does the Mobile Toolkit use its own XML markup language instead of using WML directly? The answer is that any mobile .aspx page can be used to create either WML or HTML, depending on the capabilities of the client device. The following excerpt from Search.aspx shows the source code behind the mobile search form.

<body xmlns:mobile="Mobile Web Form Controls">
  <mobile:form id="Search" runat="server">
    <P>
      <mobile:Label id="Label1" runat="server">Course Name</mobile:Label>
      <mobile:TextBox id="GCName" runat="server"></mobile:TextBox>
      <mobile:Label id="Label2" runat="server">City</mobile:Label>
      <mobile:TextBox id="GCCity" runat="server"></mobile:TextBox>
      <mobile:Label id="Label3" runat="server">State</mobile:Label>
      <mobile:TextBox id="GCState" runat="server"></mobile:TextBox>
      <mobile:Label id="Label4" runat="server">Country</mobile:Label>
      <mobile:TextBox id="GCCountry" runat="server"></mobile:TextBox>
      <mobile:Label id="Label5" runat="server">Postal Code</mobile:Label>
      <mobile:TextBox id="GCPostalCode" runat="server"></mobile:TextBox>
      <mobile:Command id="Command1" runat="server">Search</mobile:Command>
    </P>
  </mobile:form>
  . . .
   </body>

Whenever this page is requested the .NET run-time environment will determine the type and capabilities of the client. The DLL created out of the Mobile Web Form will format the result document using the most appropriate markup language. The Mobile Web Form produces the following output when called by a mobile device:

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
  <head>
    <meta http-equiv="Cache-Control" content="max-age=0"/>
  </head>
  <card id="Search">
    <do type="prev" label="Back">
      <prev/>
    </do>
    <p>
      <do type="accept" label="Search"><go href="search.aspx?__ufps=631317431691368096"
method="post">          <postfield name="__EVENTTARGET" value="Command1"/>
          <postfield name="GCName" value="$(GCName:noesc)"/>
          <postfield name="GCCity" value="$(GCCity:noesc)"/>
          <postfield name="GCState" value="$(GCState:noesc)"/>
          <postfield name="GCCountry" value="$(GCCountry:noesc)"/>
          <postfield name="GCPostalCode" value="$(GCPostalCode:noesc)"/>
        </go></do>
      Course Name
      <br/>
      <input name="GCName"/>
      City
      <br/>
      <input name="GCCity"/>
      State
      <br/>
      <input name="GCState"/>
      Country
      <br/>
      <input name="GCCountry"/>
      Postal Code
      <br/>
      <input name="GCPostalCode"/>
    </p>
  </card>
</wml>

Notice that the Mobile Web Form run time generated all the WML form logic necessary to post back to the server. It transformed each <mobile:TextBox> element into two WML elements: an <input> element to capture user input and a <postfield> element to send the result back to the server. However, the results are quite different when an HTML browser hits the page.

<html>
  <body>
    <form id="Search" name="Search" method="post" action="Search.aspx?__ufps=631317428385013792">
      <input type="hidden" name="__EVENTTARGET" value="">
      <input type="hidden" name="__EVENTARGUMENT" value="">
      <script language=javascript>
<!--
        function __doPostBack(target, argument){
          var theform = document.Search
          theform.__EVENTTARGET.value = target
          theform.__EVENTARGUMENT.value = argument
          theform.submit()
        }
// -->
      </script>
      <span>Course Name</span><br>
      <input style="" name="GCName"/><br>
      <span>City</span><br>
      <input style="" name="GCCity"/><br>
      <span>State</span><br>
      <input style="" name="GCState"/><br>
      <span>Country</span><br>
      <input style="" name="GCCountry"/><br>
      <span>Postal Code</span><br>
      <input style="" name="GCPostalCode"/><br>
      <input style="" name="Command1" type="submit" value="Search"/><br>
    </form>
  </body>
</html>

The result this time is a perfectly well-formed HTML page that uses a traditional form to collect and submit user input. Obviously, the Mobile Web Form framework does a good job of sorting out what kind of markup language should be returned to a particular client. Therefore, if Mobile Web Forms support HTML clients as well as WML clients, why not use them to build your entire UI? The problem is that the Mobile Web Forms only support controls that will work correctly on small devices. The richness of your full-blown HTML client will suffer if you use Mobile Web Forms to develop it.

Another caveat about Mobile Web Forms is that both the .aspx file and the code-behind .aspx.cs file must extend the System.Web.UI.MobileControls.MobilePage class. This limitation means that you cannot directly reuse regular Web form content that you've already developed to drive your Mobile Web Forms. Without restructuring your business logic, you might need to rewrite a significant amount of content when building your Mobile Web Forms.

Now let's take a closer look at the search.aspx.cs file (complete code is available on the CD), which implements the MobileWebForm1 class that the Search.aspx file extends. Much of this file is auto-generated by the .NET Developer Studio as controls are added to the form. All the control declarations at the top of the file were added this way.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.Mobile;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.MobileControls;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace MobileClient
{
  /// <summary>
  /// Summary description for MobileClient.
  /// </summary>
  public class MobileWebForm1 : System.Web.UI.MobileControls.MobilePage
  {
    protected System.Web.UI.MobileControls.Form Search;
    protected System.Web.UI.MobileControls.TextBox GCName;
    protected System.Web.UI.MobileControls.Label Label2;
    protected System.Web.UI.MobileControls.TextBox GCCity;
    protected System.Web.UI.MobileControls.TextBox GCState;
    protected System.Web.UI.MobileControls.Label Label3;
    protected System.Web.UI.MobileControls.Label Label4;
    protected System.Web.UI.MobileControls.TextBox GCCountry;
    protected System.Web.UI.MobileControls.Label Label5;
    protected System.Web.UI.MobileControls.TextBox GCPostalCode;
    protected System.Web.UI.MobileControls.List gcList;
    protected System.Web.UI.MobileControls.Form SearchResult;
    protected System.Web.UI.MobileControls.Command Command1;
    protected System.Web.UI.MobileControls.Form GolfCourseDetail;
    protected System.Web.UI.MobileControls.Label Label6;
    protected System.Web.UI.MobileControls.Label Label7;
    protected System.Web.UI.MobileControls.Label Label8;
    protected System.Web.UI.MobileControls.Label Label9;
    protected System.Web.UI.MobileControls.Label DetailName;
    protected System.Web.UI.MobileControls.Label DetailDescription;
    protected System.Web.UI.MobileControls.Label Label1;
    protected System.Web.UI.MobileControls.Call DetailTelephone;
    protected System.Web.UI.MobileControls.TextView DetailAddress;

The GolfCourseService variable is declared as a protected instance property. Taking this step avoids the performance penalty inherit in reinstantiating this variable each time the client calls an event handler.

    protected localhost.GolfCourseService gs;
    public MobileWebForm1()
    {
      Page.Init += new System.EventHandler(Page_Init);
    }

The GolfCourseService variable is instantiated once on page load here.

    private void Page_Load(object sender, System.EventArgs e)
    {
      gs = new localhost.GolfCourseService();
    }
    private void Page_Init(object sender, EventArgs e)
    {
      //
      // CODEGEN: This call is required by the ASP.NET Windows Form
//Designer.
      //
      InitializeComponent();
    }
    #region Web Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      this.Command1.Click += new
System.EventHandler(this.Command1_Click);
      this.Search.Activate += new
System.EventHandler(this.Form1_Activate);
      this.gcList.ItemCommand += new System.Web.UI.
MobileControls.ListCommandEventHandler(this.gcList_ItemCommand);
      this.Load += new System.EventHandler(this.Page_Load);
    }
    #endregion
    private void Form1_Activate(object sender, System.EventArgs e)
    {
    }
    private void Command1_Click(object sender, System.EventArgs e)
    {

This method is called when the user clicks the Submit button on the search form. The method sets the form's ActiveForm property to the SearchResult form. This action will cause the search result page to be returned to the client upon the completion of this method call. Next the method gathers the search criteria entered by the user and prepares the arguments required by the FindGeneric( ) Web method.

      this.ActiveForm = SearchResult;
      String[] fields = new String[5];
      String[] values = new String[5];
      fields[0] = "name";
      string nameValue = Request.QueryString.Get("name");
      if (nameValue == null || nameValue == "")
      {
        nameValue = "*";
      }
      values[0] = nameValue;
      fields[1] = "city";
      string cityValue = Request.QueryString.Get("city");
      if (cityValue == null || cityValue == "")
      {
        cityValue = "*";
      }
      values[1] = cityValue;
      fields[2] = "state";
      string stateValue = Request.QueryString.Get("state");
      if (stateValue == null || stateValue == "")
      {
        stateValue = "*";
      }
      values[2] = stateValue;
      fields[3] = "country";
      string countryValue = Request.QueryString.Get("country");
      if (countryValue == null || countryValue == "")
      {
        countryValue = "*";
      }
      values[3] = countryValue;
      fields[4] = "postalCode";
      string postalCodeValue = Request.QueryString.Get("postalCode");
      if (postalCodeValue == null || postalCodeValue == "")
      {
        postalCodeValue = "*";
      }
      values[4] = postalCodeValue;

The method calls the FindGeneric( ) Web method associated with the GolfCourseService Web Service. The Web method returns an array of GolfCourse objects, which are inserted into a List control on the Mobile Web Form.

      localhost.GolfCourse[] gc = gs.FindGeneric(fields,values);
      for (int i=0 ; i < gc.Length; i++)
      {
        gcList.Items.Add(gc[i].name);
        gcList.Items[i].Value = gc[i].id.ToString();
      }
    }

This event handler is called when the user selects one of the items from the list of golf courses returned by the search. Once again, the event handler switches the ActiveForm to the GolfCourseDetail page. The handler calls the GetCourseDetail( ) Web method to retrieve detailed information about the selected GolfCourse. The course details are loaded into the label controls that have been pasted on the GolfCourseDetail form. Finally the Call control's PhoneNumber property is set to the value of the course's telephone property. This feature lets the user auto-dial the golf course with the click of a button.

    private void gcList_ItemCommand(object source, System.Web.UI.
MobileControls.ListCommandEventArgs e)
    {
      ActiveForm = GolfCourseDetail;
      String gcid = e.ListItem.Value;
      localhost.GolfCourse gc = gs.GetCourseDetail(gcid);
      DetailName.Text = gc.name;
      DetailDescription.Text = gc.description;
      DetailAddress.Text = gc.address.street+"<br/>
"+gc.address.city+", "+gc.address.state+" "+gc.address.
postalCode+"<br/>"+gc.address.country;
      DetailTelephone.PhoneNumber = gc.telephone;
    }
  }
}

Now let's move on to the search.aspx page. The forms themselves are straightforward. They are created by dragging Mobile Web Forms controls onto the page and editing their properties. Figure 13-4 shows how the Mobile Web Form looks after all of the controls have been inserted and positioned correctly.

Figure 13-4 All three Golf Reservation System Mobile Web Forms using the Mobile Explorer Toolkit.

Next we look at the XML source that lies beneath the GUI form display. Search.aspx contains all the mobile XML code the .NET Developer Studio creates for us.

<%@ Register TagPrefix="mobile" Namespace="System.Web.UI.
MobileControls" Assembly="System.Web.Mobile" %>
<%@ Page language="c#" Codebehind="Search.aspx.cs" Inherits=
"MobileClient.MobileWebForm1" AutoEventWireup="false" %>
<meta content="Microsoft Visual Studio 7.0" name="GENERATOR">
<meta content="C#" name="CODE_LANGUAGE">
<meta content="Mobile Web Page" name="vs_targetSchema">

The search form contains the labels and text boxes needed to query the Golf Reservation System golf course database.

<body xmlns:mobile="Mobile Web Form Controls">
  <mobile:form id="Search" runat="server">
    <P>
      <mobile:Label id="Label1" runat="server">Course Name
</mobile:Label>
      <mobile:TextBox id="GCName" runat="server"></mobile:TextBox>
      <mobile:Label id="Label2" runat="server">City</mobile:Label>
      <mobile:TextBox id="GCCity" runat="server"></mobile:TextBox>
      <mobile:Label id="Label3" runat="server">State</mobile:Label>
      <mobile:TextBox id="GCState" runat="server"></mobile:TextBox>
      <mobile:Label id="Label4" runat="server">Country</mobile:Label>
      <mobile:TextBox id="GCCountry" runat="server"></mobile:TextBox>
      <mobile:Label id="Label5" runat="server">Postal Code
</mobile:Label>
      <mobile:TextBox id="GCPostalCode" runat="server"></mobile:TextBox>
      <mobile:Command id="Command1" runat="server">Search</mobile:Command>
    </P>
  </mobile:form>

The SearchResult form contains only a single mobile List control, gcList. The list is populated at runtime with the results of the FindGeneric( ) Web method call.

  <mobile:form id="SearchResult" runat="server">
    <P>
      <mobile:List id="gcList" Runat="Server"></mobile:List>
    </P>
  </mobile:form>

The golf course detail form displays detailed information about a specific golf course. Notice the inclusion of the <mobile:Call> element. This control allows you to take advantage of a cell phone's dialing capability.

  <mobile:form id="GolfCourseDetail" runat="server">
       <P>
    <mobile:Label id="Label6" runat="server" Font-Bold="True">
Name
</mobile:Label>
    <mobile:Label id="DetailName" runat="server"></mobile:Label>
    <mobile:Label id="Label7" runat="server" Font-Bold="True">
Description
</mobile:Label>
    <mobile:Label id="DetailDescription" runat="server">
</mobile:Label>
    <mobile:Label id="Label8" runat="server"
Font-Bold="True">Address</mobile:Label>
    <mobile:TextView id="DetailAddress" runat="server"></mobile:
TextView>
    <mobile:Label id="Label9" runat="server" Font-Bold="True">
Telephone
</mobile:Label>
    <mobile:Call id="DetailTelephone" runat="server">
</mobile:Call>
    </P>
  </mobile:form>
</body>

Figure 13-5 shows how this implementation looks on the Mobile Explorer.

Figure 13-5 The course search form shown inside the Mobile Explorer.

Certainly, the Mobile Explorer Toolkit is a useful tool for developing mobile clients quickly. The only downside to the product is that it forces you to assemble an entirely new library of Mobile Web Forms, often duplicating code you've written for other clients. This duplicate code base might become difficult to maintain over time.

Using The Golf Reservation System Skin Architecture

We will now explore an alternative implementation of a mobile Golf Reservation System client, one that uses the application's "skins" approach to UI design to incrementally provide WML support. The project requires two steps:

  1. Modify the controller.asp script to detect the browser type. If it supports WAP, load the WML skin and use it instead of the HTML skin for transforming the page content. The controller must also be sensitive to the content type of the result document. It must be set to text/vnd.wap.wml.
  2. Create a new XSLT skin targeted at WAP devices and place it in the /xslt directory of the Golf Reservation System client application. This skin will only produce WML code.

The required modification to the controller.asp script is trivial. We just need to add the following lines of code before loading the XSLT document:

If InStr(1,Request.ServerVariables("ALL_HTTP"),"text/vnd.wap.wml",1) Then
  skin = "mobile"
  Response.ContentType = "text/vnd.wap.wml"
End if

This code will force the controller to load the "mobile" skin to perform the final page transformation, and sets the MIME type of the response to the correct setting. Note the over simplicity of the browser detection algorithm used here. It is used for demonstration purposes only.

The Golf Reservation System XML content follows an object-based schema instead of a presentation-based schema, which makes repurposing small devices much easier. Because our content and presentation layers are cleanly divided this way, we will be able to implement a mobile interface without making any changes to the content layer. Thus, the code that interacts with the business layer of the application will remain intact and will not need to be replicated.

To begin we need to create templates for structural elements in the Golf Reservation System client schema that are common to multiple pages. These elements include <document>, <header>, <section>, <form>, <input> (text and hidden), <choice>, <option>, <properties>, and <property>. With the exception of form, the templates in the WML skin will be extensively reduced versions of their HTML counterparts. We don't need to worry about building complicated tables to handle layout or aesthetic style sheets to make the client look pretty. We are striving for functionality, first and foremost.

As we see in Mobile.xsl, the <document> and <header> transformation templates act as little more than placeholders.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
  <xsl:output method="xml" omit-xml-declaration="yes" indent="no" encoding="" doctype-public="-//WAPFORUM//DTD WML1.2//EN"
doctype-system="http://www.wapforum.org/DTD/wml12.dtd" />
  <xsl:template match="document">
    <wml>
      <xsl:apply-templates />
    </wml>
  </xsl:template>
  <xsl:template match="header">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="header/title"></xsl:template>
  <xsl:template match="header/description"></xsl:template>

The form template needs to accommodate the WML form posting semantics. First it needs to create <do> and <go> tags to submit the form back to the server. The template must also create a <postfield> element for each <input> element that appears in the source document. Textual input fields are also transformed into WML <input> elements (Hidden inputs require no WML <input> element; their values are inserted directly into the corresponding <postfield> element).

  <xsl:template match="form">
    <card id="{generate-id()}" title="{.//header/title}">
      <do type="accept" label="Accept">
        <go href="{@action}">
          <xsl:for-each select=".//input|.//choice">
            <postfield name="{@name}">
              <xsl:choose>
                <xsl:when test="@type='hidden'">
                  <xsl:attribute name="value">
                    <xsl:value-of select="@value" />
                  </xsl:attribute>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:attribute name="value">$(<xsl:value-of select="@name" />)</xsl:attribute>
                </xsl:otherwise>
              </xsl:choose>
            </postfield>
          </xsl:for-each>
        </go>
      </do>
      <p>
        <fieldset title="{section/header/title}">
          <xsl:apply-templates select="section/*" />
        </fieldset>
      </p>
    </card>
  </xsl:template>
  <xsl:template match="section[count(courseDetail) = 0]">
    <card id="{generate-id()}" title="{header/title}">
      <p>
        <xsl:apply-templates />
      </p>
    </card>
  </xsl:template>
  <xsl:template match="view">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="properties">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="property">
    <xsl:value-of select="@description" />
    <xsl:apply-templates />
    <br />
  </xsl:template>

Here the template creates the WML input elements. The <choice> element in the source document is converted into a WML <select> element.

  <xsl:template match="input[@type='text']">
    <input name="{@name}" value="{@value}" />
  </xsl:template>
  <xsl:template match="choice">
    <select name="{@name}">
      <xsl:apply-templates />
    </select>
  </xsl:template>
  <xsl:template match="option">
    <option value="{@value}"><xsl:value-of select="." /></option>
  </xsl:template>

These general tags are actually enough to handle the course search view. Let's load the view into the Mobile Explorer and see the results. The URL should be http://localhost/GolfReservationSystemClient/controller.asp?view=courseSearch.

Figure 13-6 shows the course search page on the Mobile Explorer.

Figure 13-6 The course search view shown in the Mobile Explorer.

Transforming the courseSearchResult view is just as easy. All we have to do is match the template and create hyperlinks for all of the <courseListItem> elements. We will also show the course's city and state below each link.

WAP devices have a larger degree of freedom when rendering WML than browsers do when rendering HTML. Various device restrictions make strict specification of presentation rules impossible. For example, identical <select> lists can appear entirely different on two different WAP devices.
  <xsl:template match="courseList">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="courseListItem">
    <a href="controller.asp?view=courseDetail&amp;id={id}">
      <xsl:value-of select="name" />
    </a>
    <br />
    <xsl:value-of select="city" />, <xsl:value-of select="state" /><br />
  </xsl:template>

Figure 13-7 illustrates what this page looks like on a mobile device. The page can be reached by submitting the course search form.

Figure 13-7 The course search result view shown in the Mobile Explorer.

The courseDetail template is responsible for laying out specific course information in an organized fashion. This template will create a deck with two cards in it: one to show the course's information and another to request a tee time. The courseDetail XML content page contains another section describing hole yardages and difficulty, but the mobile skin ignores this information as it is too big to display neatly on a small screen and is not critical to the page.

The teetime reservation form is generated automatically by the <form> transformation logic we looked at earlier. We do need to supply a link to the card, though. The <do> and <go> tags in the following listing prepare the hyperlink in the result WML document.

  <xsl:template match="section[count(courseDetail) > 0]">
    <xsl:apply-templates select="courseDetail" />
  </xsl:template>
  <xsl:template match="courseDetail">
    <xsl:for-each select="courseDetailBlock">
      <xsl:if test="courseItem[@type != 'holeInfo']">
        <card id="{generate-id()}" title="{@description}">
          <do type="accept" label="Schedule a Tee Time">
            <go href="#{generate-id(//form)}" />
          </do>
          <xsl:apply-templates />
        </card>
      </xsl:if>
      <xsl:if test="form">
        <xsl:apply-templates />
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

Here we extract the name and address of the course for viewing.

  <xsl:template match="courseItem[@type='courseInfo']">
    <p>
      <xsl:value-of select="//courseDetail/name" /><br />
      <xsl:value-of select="address/address1" /><br />
      <xsl:value-of select="address/city" />, <xsl:value-of select="address/state" /> <xsl:value-of select="address/postalCode"/>
<br />
      <xsl:value-of select="address/country" />
    </p>
  </xsl:template>

Figure 13-8 shows the tee time scheduling screen.

Figure 13-8 The tee time scheduling screen, the second card in the course detail deck, shown on the Mobile Explorer.

The teeTimes view displays a list of available tee times on the selected date and course. Each list item is wrapped inside a hyperlink to the scheduleTeeTime page. The anchor tag passes in all the tee time, course, and golfer information.

  <xsl:template match="view[teeTimes]">
    <xsl:apply-templates select="teeTimes" />
  </xsl:template>
  <xsl:template match="teeTimes">
    Choose a time below<br />
    <xsl:for-each select="teeTime">
      <xsl:if test="not(taken)">
        <a href="controller.asp?view=scheduleTeeTime&amp;golferId={../golferId}&amp;courseId={../courseId}&amp;teeTime={teeTimeSystemDate}"><xsl:value-of select="date" /> - <xsl:value-of select="time"
/></a><br />
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

The teeTimes view is shown in Figure 13-9.

Figure 13-9 The viewTeeTimes page shown in the Mobile Explorer.

Finally, assuming everything posts correctly, we show the user a friendly note of success. The teeTimeDetail page also reviews the tee time's logistics.

  <xsl:template match="teeTimeDetail">
    A new tee time has been created for:<br/>
    <xsl:value-of select="golfer/name"/><br/>
    <xsl:value-of select="course/name"/><br/>
    <xsl:value-of select="date"/> <xsl:value-of select="time"/>
  </xsl:template>
</xsl:stylesheet>

The primary advantage to the skin approach is that we get to preserve all the logic and content that went into the original HTML client. We did not need to rewrite any XML Web services code, as all our interaction was limited to the data coming out of the content layer.

For a production system this example should be extended to use the Content Adapter model discussed earlier. This action would provide maximum flexibility and control over style and layout. The Golf Reservation System client skin library can be augmented with new transformations for voice or television.


Conclusion

In this chapter we explored cross-device application development opportunities. We examined the trials and tribulations inherent in developing cross-device applications as well as some design patterns that ease development stress. We looked at a few popular specifications on the Internet today. Finally we built two completely different implementations of the same wireless client. The first used the Mobile Explorer Toolkit. The second utilized the application's native skins architecture to deliver the same functionality.

Cross-device programming is a hot issue for developers today. For those of you who are just beginning to design the next generation of enterprise XML applications, make sure that you keep client extensibility in the forefront of your thinking. The design decisions you make today might save you or cost you months of development time later.

The logical partitioning of your application is a critical factor in code reusability. Just as your business logic should be able to support different types of clients, so should your UI model. By using XML and XSLT to separate the content and presentation layers of your UI, you can actually plug in new presentation rules to support new and different devices. In Chapter 14 we will see how legacy systems can be integrated into modern computing environments using XML.


More About SOAP

In previous chapters we exposed you to SOAP and showed how you can use it as part of your toolkit of Web Service applications. Because SOAP is a critical component of the .NET Framework and other Web-centric technologies, you should understand not only the syntax and semantics, but also the principles behind it. In this chapter we will go to another level of detail on SOAP—how it works, how messages look, and how you can work directly with them.

In the chapter we will cover the details of the SOAP <Envelope>, breaking it down into the <Header>, <Body>, and encoding methods for sending SOAP messages between servers. We will spend some time using SOAP by building a few small clients and servers and showing you how they communicate and work.


The SOAP <Envelope>

A SOAP message is an XML document that consists of a mandatory SOAP <Envelope> root element, an optional <Header> element, and a mandatory <Body> element. Think of this as an envelope used to mail a letter to a friend across the country. The SOAP envelope is analogous to the paper envelope—it displays the routing directions and the letter. The routing directions, or address (to and from) are represented in the <Header> element of a SOAP message. The letter itself is contained in the SOAP body. All this information is necessary to successfully transmit data via the SOAP protocol. Let's begin with a short example to illustrate how SOAP works.

In Listing 14-1, skeletal_soap.xml, we show you a skeletal SOAP message with placeholders for the <Header> and the <Body> information. In a real message these would contain the necessary routing information and payload of the exchange.

Listing 14-1 skeletal_soap.xml: A skeletal SOAP message.

<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/
encoding/">
  <SOAP-ENV:Header>
    <!-- (optional) Contextual header information... -->
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <!-- Serialized object information... -->
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

You can also define your own subelements within a SOAP <Envelope>, but they must be namespace-qualified and must come after the <Body> element.

All SOAP messages should include the proper namespace declarations so that SOAP applications can correctly process the messages. The SOAP specification defines two namespaces.

  • The SOAP <Envelope> is identified by the http://schemas.xmlsoap.org/soap/envelope/ namespace URI, which defines the standard SOAP elements and attributes used in the <Envelope>.
  • The SOAP default encoding rules and data types are identified by the http://schemas.xmlsoap.org/soap/encoding/ namespace URI.

The encodingStyle Global Attribute

The SOAP-ENV:encodingStyle attribute is used to indicate the rules used to encode the information in a particular SOAP message. The default SOAP encoding rules are identified by the http://schemas.xmlsoap.org/soap/encoding/ namespace. Like any other use of namespaces, this attribute can appear on any element. When applied, its scope is restricted to that element's contents and all child elements not overriding the attribute with their own namespace. Note that no default encoding is defined for a SOAP message, so you must include the encodingStyle attribute at least once in your SOAP messages. Typically you will include it in the <Envelope> declaration itself, as shown in the skeletal_soap.xml example.

You can define your own encoding rules. In that case you will need to define your own schema.

Now let's turn to the <Header> and <Body> elements and take a closer look at how they are specified and work.

<Header>

The <Header> element is used to encapsulate information that is not tied to a specific method invocation, but provides context information instead. Typical examples of <Header> element usage are security, transaction management, and payment information.

There are several rules defined by the SOAP specification for the <Header> element. These are as follows:

  • The <Header> element is optional, but, if present, must be the first child element in an <Envelope>.
  • The <Header> element must use the SOAP encoding rules unless the header specifies a different set of rules. This requirement is accomplished using the SOAP-ENV:encodingStyle attribute.
  • All <Header> subelements must be namespace-qualified.
  • The <Header> element can contain the SOAP-ENV:mustUnderstand attribute.

To show you an example of how to use the <Header> element, let's make a request to an imaginary language-translation service that translates arbitrary words, paragraphs, or both from one specified spoken language to another. Furthermore, suppose this is a pay-by-use service and that you will need to provide valid authentication information with every request. Listing 14-2, soap_header.xml, shows the <Header> section of such as a request.

Listing 14-2 soap_header.xml: Some example SOAP <Header> elements.

<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/
encoding/"/>
  <SOAP-ENV:Header>
    <a:Authentication xmlns:a="some-URI">
      <UserID>foo</UserID>
      <Password>an-encoded-password</Password>
    </a:Authentication>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <!-- Serialized object information... -->
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

In this example we added a custom Authentication <Header> element with a <UserID> and <Password> subelements providing the necessary authentication information. Notice that, as required by the specification, we have properly namespace-qualified our Authentication <Header> element.

mustUnderstand

In soap_header2.xml the concept of authentication is critical to the success of the service request. To ascertain that a service understands and can process a specific <Header> element, you can tag specific <Header> elements with the SOAP-ENV:mustUnderstand attribute. This attribute indicates to a SOAP-compliant processor that the associated <Header> element is critical to the processing of the method call and that, if for some reason the remote SOAP processor is unable to understand or handle it, you don't want the method call to execute. In this case the SOAP processor should simply reject the method call.

The value of the SOAP-ENV:mustUnderstand attribute is either 1 or 0. Listing 14-3, soap_header2.xml, shows the SOAP message soap_header.xml updated to include the SOAP-ENV:mustUnderstand attribute.

Listing 14-3 soap_header2.xml: The SOAP-ENV:mustUnderstand attribute.

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  SOAP-ENV:encodingStyle=" http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Header>
    <a:Authentication xmlns:a="some-URI" SOAP-ENV:mustUnderstand="1">
      <UserID>foo</UserID>
      <Password>an-encoded-password</Password>
    </a:Authentication>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <!-- Serialized object information... -->
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

With this change in our SOAP message, a SOAP-compliant processor will attempt to process this message only if it can understand our custom <Authentication> <Header> element. If not, the processor will reject this message with a proper response message.

actor

When a SOAP message travels from the originator to its ultimate destination, it does not need to travel directly between the source and destination. The message might pass through a set of SOAP intermediaries, each receiving and then forwarding the message to the next receiver, until the message reaches its final destination. Therefore, not all parts of a SOAP message can be intended for the final destination; some must be directed at one or more of the intermediaries on the message path.

You use the SOAP-ENV:actor attribute to allow a SOAP message to indicate that <Header> information that is relevant to a specific recipient. The value of the SOAP-ENV:actor attribute is a URI used to identify the recipient of the <Header> element. The SOAP specification defines a special URI, http://schemas.xmlsoap.org/soap/actor/next, to indicate that the associated <Header> element is intended for the first SOAP recipient, intermediary or otherwise.

Now that we have covered the <Header> and how it is used to provide contextual information for a SOAP message, we can discuss the <Body> element of the SOAP message.

<Body>

The <Body> element contains the actual serialized data of the method call itself. The <Body> element is also used for a method call's response, which will either be the information related to a valid request or a SOAP fault if the call failed. The SOAP specification defines three types of <Body> elements.

  • The Call <Body> contains the information required for a method call, such as the method name and arguments.
  • The Response <Body> contains the response information for a successful method call.
  • The Fault <Body> contains the fault code and other information for an unsuccessful method call.

Call <Body>

The first child element of a Call <Body> element is labeled according to the method name. The embedded elements are the serialized arguments, with each argument named according to the method signature. For example, in our TranslationService method call we might have a method with the following C# signature:

string TranslateText(string SourceLanguage, string TargetLanguage, string Text)

Imagine that you now want to make the following equivalent method call:

// Translates an English sentence to French
string translatedText = TranslateText( "en", "fr", "I speak French" );

Listing 14-4, soap_callbody.xml, shows the equivalent SOAP message.

Listing 14-4 soap_callbody.xml: SOAP Call <body> example.

<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Header>
    <a:Authentication xmlns:a="some-URI" SOAP-ENV:mustUnderstand="1">
      <UserID>foo</UserID>
      <Password>an-encoded-password</Password>
    </a:Authentication>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <m:TranslateText xmlns:m="some-URI">
      <SourceLanguage>en</SourceLanguage>
      <TargetLanguage>fr</SourceLanguage>
      <Text>I speak French</Text>
    </m:TranslateText>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Note that, just as in the <Header> element, the <Body> element is namespace-qualified as required by the SOAP specification. You don't need to qualify the argument elements, though, because they are assumed to be identified by the enclosing method element's namespace.

Response <Body>

A Response <Body> contains information the SOAP service returns to the caller after a successful method call. Listing 14-5, soap_responsebody.xml, shows you a sample successful response to the method call made in soap_callbody.xml.

Listing 14-5 soap_responsebody.xml: SOAP Rresponse <Body> example.

<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <m:TranslateTextResponse xmlns:m="some-URI">
      <Text_fr>Je parle Francais</Text_fr>
    </m:TranslateTextResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The custom is to append the word Response to the original method name. As in the method call, the name of the return arguments, which in soap_responsebody.xml is <Text_fr>, is immaterial as far as the specification is concerned. Finally the element is namespace qualified.

<Fault> in <Body>

When a method call fails, instead of a normal Response <Body>, a SOAP processor will use a <Fault> occurrence in the <Body> of the response SOAP message, as shown in Listing 14-6, soap_faultbody.xml.

Listing 14-6 soap_faultbody.xml: SOAP <Fault> in the <Body>.

<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <SOAP-ENV:Fault>
      <faultcode>SOAP-ENV:Server</faultcode>
      <faultstring>Translator not found</faultstring>
      <detail xmlns:e="some-URI">
        <e:ErrorCode>
          014
        </e:ErrorCode>
        <e:ErrorMsg>
          No suitable Translator found for "en-fr" translation
        </e:ErrorMsg>
      </detail>
    </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The SOAP specification requires that <Fault> be the first child element of the <Body> element.

The SOAP specification defines the following subelements:

  • The mandatory <faultcode> element can be used by applications for algorithmically identifying the fault. The <faultcode> values are defined in an extensible manner similar to the 1xx, 2xx, 3xx, and other basic HTTP response codes. However, instead of numeric values the codes are defined as XML qualified names. A . (dot) character separates fault values indicating that what is to the left of the dot is a more generic fault code value than the value to the right (for example, Client.Authentication). SOAP specification defines the fault codes shown in Table 14-1.
  • The mandatory <faultstring> element provides a human-readable explanation of the fault.
  • The optional <faultactor> element provides information about what caused the fault to happen within the message path.
  • The mandatory <detail> element provides application-specific error information related to the processing of the <Body> element. A detail entry, which is an immediate child element of the <detail> element, must be a fully qualified name.
Table 14-1 Fault Codes Defined in SOAP
Fault code | Meaning
VersionMismatch | The processor found an invalid namespace for the SOAP element.
MustUnderstand | A <Header> element tagged with the SOAP-ENV: mustUnderstand attribute was either not understood or properly obeyed by the processor.
Client | The message was incorrectly formed or did not contain the appropriate information to succeed. Generally, the message should not be sent again without a change correcting the problem.
Server | The message could not be processed by the processor for reasons not directly attributable to the contents of the message itself, but to the processing of the message.

In addition to these standard elements you can include your own custom subelements within the <Fault> element as long as they are namespace-qualified.

Encoding

So far we've only talked about the structure of a SOAP message and none of the other aspects. In this section we will look at how the arguments associated with a method call are actually encoded, from both a simple and a compound perspective, as well as array encoding and default values.

As we mentioned earlier, you can use the SOAP-ENV:encodingStyle attribute to indicate the encoding style used. SOAP's standard encoding style, the schema of which can be found at http://schemas.xmlsoap.org/soap/encoding/ , is modeled after typical programming languages like C or C++ (and now C#).

The SOAP specification also refers to the "XML Schema Part 1: Structures" (www.w3.org/TR/xmlschema-1/) and "XML Schema Part 2: Datatypes" (www.w3.org/TR/xmlschema-2/) for encoding message schemas.

In SOAP you can use only elements to represent values within a <Body>. Using attributes for passing arguments is illegal. The elements must be either a simple scalar type defined by the "XML Schema Part 2: Datatypes" specification, an element that encapsulates a SOAP array, or an element whose structure you can ascertain with the help of a schema.

Simple Types

Simple types, such as integers, strings, and floating-point numbers, can be directly encoded in SOAP as elements. The schema for the elements defines the actual types, not the actual SOAP message. In soap_callbody.xml, for example, the three string arguments of the method TranslateText are encoded directly as simple types.

Compound Types

Compound types are encoded like structures or even classes in some programming languages, like C++, C#, and Java. The containing element holds the structure information while the elements within are either simple types or other compound types. The containing element is always qualified with a namespace, while all the contained elements are unqualified. For example, examine the simple C structure in Listing 14-7, c_struct.txt.

Listing 14-7 c_struct.txt: A simple C structure.

struct tag_Person
{
  string name;
  double age;
} Person;

Now take a look at the corresponding encoded SOAP in Listing 14-8, compound_type.xml.

Listing 14-8 compound_type.xml: An example of Compound_Encoding.

<Person xmlns="someURI">
  <name>Harry</name>
  <age>37</age>
</Person>

Array

The final major type of SOAP encoding is array, which builds on the other types to produce ordered lists of simple or compound datatypes. The special SOAP-ENC:arrayType attribute is used to distinguish a SOAP array from other datatypes. Listing 14-9, array_type.xml, shows how an array of the Person datatype declared in compound_type.xml can be encoded in SOAP.

Listing 14-9 array_type.xml: SOAP array example

<PersonList SOAP-ENC:arrayType="Person[2]">
  <Person>
    <name>Tom</name>
    <age>27</age>
  </Person>
  <Person>
    <name>Dick</name>
    <age>30</age>
  </Person>
</PersonList>

Default Values

Some programming languages, such as C++ and C#, have the concept of default arguments. SOAP by itself does not specify default values. However, with the help of proper schema definition, a SOAP consumer can automatically omit default arguments from the message packet. On the receiving side, a SOAP processor can also automatically invoke the remote method itself with the appropriate default information inserted.

Now that you've learned how to construct SOAP messages, the following section will show you how to implement applications that can produce and consume them.


Developing SOAP Applications

The Microsoft .NET Framework provides comprehensive support for developing both SOAP clients/consumers and servers/producers using a variety of different technologies and methods. In this section we take a closer look at some ways you can develop SOAP applications.

Using Web Services

ASP.NET offers excellent support for creating XML Web services based on SOAP servers. Support for XML Web services provided through ASP.NET is contained within .asmx files, which are text file, similar to .aspx files. Over the next few pages we will look at some simple examples that build both clients and servers. These, of course, will interact as XML Web services.

The Server

Listing 14-10, hello.asmx, shows a simple Web Service .asmx file. This program returns nothing more than a string to the requesting client.

Listing 14-10 hello.asmx: A simple Web Service.

<%@ WebService Language="C#" Class="Hello" %>
using System;
using System.Web.Services;
public class Hello : WebService
{
  [WebMethod] public String SayHello( String Name )
  {
    return ( "Hello " + Name );
  }
}
This particular Web Service is implemented in C#, but you could also use any other language built on the .NET Framework, such as Visual Basic .NET.

The first line in hello.asmx declares that this is an XML Web service and sets the language (C# in this case) used to implement this service. Next we import the necessary System.Web.Services namespace and declare that our class is derived from the WebService base class. Finally, to indicate that our GetStockPrice method will be accessible as part of the service, we marked it with the [WebMethod] custom attribute.

To allow client applications to consume this Web Service you need to create a proxy class DLL. This DLL wraps all the code and calls in the associated Web Service into a simple method call similar to a COM object. With the .NET Framework SDK, you will use a tool called WebServiceUtil.exe to perform this task. Use the following:

WebServiceUtil /c:proxy /pa:http://localhost/Hello.asmx?SDL /n:nsHello

to generate a proxy class called Hello.cs. Next you need to compile this Hello.cs into a DLL with the following command:

csc /out:Hello.dll /t:library /r:system.data.dll /r:system.web.
services.dll /r:system.xml.serialization.dll Hello.cs

The Client

The Hello Web Service hello.asmx can be accessed by a client that knows how to consume SOAP messages. For example, we will develop a simple ASP.NET page that will consume this XML Web service. This is shown in Listing 14-11.

Listing 14-11 hello_client.aspx: An ASP.NET consumer of the Hello Web Service.

<%@ Import Namespace="nsHello" %>
<html>
  <script language="VB" runat="Server">
    Sub Greeting( Src as Object, E as EventArgs )
      Dim h as New Hello
      lblGreeting = h.SayHello( edtName.Value )
    End Sub
  </script>
  <body>
    <form runat="SERVER">
      Your name:
      <ASP:INPUT ID="edtName" TYPE="Text" RUNAT="Server"/><br/>
      <ASP:BUTTON TEXT="Say Hello!" onclick="Greeting"/>
    </form>
    <h1 ID="lblHello" RUNAT="Server"/>
  </body>
</html>

This ASP.NET first imports the Hello Web Service proxy class DLL using the namespace defined. The actual call to the Hello Web Service happens in the Greeting procedure in HelloClient.aspx. The New keyword is used to instantiate the object. Once instantiated, we simply invoke the SayHello method to do the job.

For those of you familiar with Microsoft programming methodology and technology, this approach is similar to how you would use a COM object. Behind the scene the Hello Web Service proxy class DLL packages our method call within a SOAP message and transfers it over the wire to our Hello Web Service for processing. The Hello Web Service then parses the SOAP message and invokes the SayHello method. The result is then packaged as a SOAP response message and once again passed by the proxy class DLL. And all this time you think you are making only a simple method call!

Using Components

Microsoft .NET Remoting enables developers to produce remote-managed, native COM/COM+, and serviced components (managed components serviced by COM+ services). What makes the .NET implementation important is that these components are built as SOAP endpoints from any process, including console applications, GUI applications, Windows NT Services, and IIS. Because they are built on this standard for communication, the number of applications that ultimately can interface with the components is increased, and chances for success in heterogeneous environments are improved.

In this section we will look at both server and client components. We are going to step through some simple examples to show you how they work and how easy it is to build them.

The Server

Listing 14-12, hello.cs, shows a simple .NET managed component that exposes the SayHello method that will be accessible through SOAP.

In hello.cs the Hello object is remotable by extending the MarshalByRefObject class. This allows the SayHello method to be accessed through SOAP when this object is exposed by a suitable host that acts as the SOAP endpoint. An example of such a host is shown in Listing 14-13, hello_host.cs, which builds a service waiting for a connection. The user is able to stop the service, as you will see near the end of the listing, by pressing the Enter key.

Listing 14-12 hello.cs: A managed component.

using System;
namespace nsHello
{
  /* Define the Service */
  public class Hello : MarshalByRefObject
  {
    /* This method will be called remotely by our client */
    public String SayHello( String Name )
    {
      return ( "Hello " + Name );
    }
  }
}

Listing 14-13 hello_host.cs: Creates a service listening for connections.

using System;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.HTTP;
public class HelloHost
{
  public static void Main( String[] args )
  {
    /* Manually load the http channel. 1099 is the port # */
    ChannelServices.RegisterChannel( new HTTPChannel( 1099 ) );
    /* Register the wellknown server type */
    RemotingServices.RegisterWellKnownType(
      "nsHello",                       // Assembly
      "nsHello.Hello",                 // Full type name
      "host/Hello.soap",               // URI
      WellKnownObjectMode.Singleton ); // Object Mode
    /* Wait until the user wants to exit */
    Console.WriteLine( "Listening for requests - Press ENTER to exit" );
    String keyState = Console.ReadLine();
  }
}

In the .NET Framework messages are transported to and from remote objects through Channels Objects that encapsulate the underlying network protocols used to make the client/server communication. In hello_host.cs we indicated that we would like to use the built-in HTTP Channel, which transports messages using the SOAP protocol.

Now that we have our code in these two files, we must compile them. As mentioned earlier, this task can be accomplished using the csc C# compiler. A simpler process, however, would be to use a makefile. These files contain a list of compilation commands used to compile one or more code files. For our example, use Listing 14-14, makefile, to build hello.cs and hello_host.cs.

Listing 14-14 makefile: A makefile for building hello_host.cs and hello.cs.

all: hello_host.exe hello.dll
hello_host.exe: hello_host.cs
    csc /r:System.Runtime.Remoting.dll hello_host.cs
hello.dll: hello.cs
    csc /t:library hello.cs

The Client

Now that the server is created we can focus on the client. Listing 14-15, hello_client.cs, is an application that will consume the service exposed by our hello_host.cs to invoke the SayHello remote method.

Listing 14-15 hello_client.cs: A consumer of the hello_host.cs.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.HTTP;
using nsHello;
public class HelloClient
{
  public static Main( String[] args )
  {
    /* Obtain a Proxy to the SOAP URL */
    Hello h = (Hello)Activator.GetObject(
                 typeof(Hello),
                 "http://localhost:1099/host/Hello.soap" );
    /* The following occurs over SOAP to HelloHost */
    Console.WriteLine( h.SayHello( "Dick" ) );
  }
}

In hello_client.cs we used the GetObject method instead of the new keyword to activate the remote object because we are manually activating the object. Had we used the remote configuration file, we could have used the New keyword to the same effect.

As with the previous example, we need to compile the application. The following command will build the hello_client.cs:

    csc /r:System.Runtime.Remoting.dll /r: hello.dll hello_client.cs

Once both the server and client applications are built, we can test them by first running hello_host.exe in one window and then running hello_client.exe in another. You will see hello_client.exe respond with the message "Hello Dick" on your screen.


Conclusion

In this chapter we've looked at what a SOAP message is and how you can use it to encode object information for making remote method calls. We also looked briefly at how you can use the powerful technologies provided by the .NET Framework to build distributed applications using SOAP, and we've included some quick examples to get your started.

By now you should feel more comfortable with SOAP and how it works. There is much more to know, so if your thirst for more information is great, we highly recommend you read through the W3C Note defining SOAP. You can use SOAP for many different projects, even if you're not implementing a true .NET-compatible system.

In the next chapter we will look into BizTalk Server, a suite of applications introduced in Chapter 7. We will cover several of the tools that come with BizTalk Server to help you define and orchestrate your entire messaging process.


Exploring BizTalk Server

Back in Chapter 7 you were introduced to the Microsoft BizTalk Framework and the Microsoft BizTalk Server in relation to messaging XML documents back and forth between servers and potentially companies. In that chapter you learned about the details of the Microsft .NET Framework and how to use the BizTalk Messaging Manager and BizTalk Document Tracking applications to control and route your messages. But as we alluded to then, that only touches the tip of the iceberg of what the BizTalk Server includes and can be used for.

BizTalk Server 2000 became available toward the end of 2000. Honestly, many people do not fully grasp the potential of BizTalk Server and what it truly is. If you think of this server and its corresponding applications, which we will cover shortly, from a functional standpoint, it becomes clearer as to how it can be used within most companies today.

For instance, if you perform any exchanges with an external partner or affiliate companies or internally, BizTalk Server could be used to manage, track, and greatly reduce the technology and manpower overhead needed to maintain such relationships. Also, if you have data stored in legacy systems, be it mainframes or file systems, BizTalk Server can be used to integrate with these applications, abstracting and hosting the functionality needed to access the data they store. This capability allows you to avoid any "rip-and-replace" approaches you might have previously considered. BizTalk Server can become the legs your legacy data needs to walk again.

These are just a few examples of how BizTalk Server can be used within a company. In this chapter we are not only going to extend our discussion on the server portion from Chapter 7, we are also going to introduce you to the other tools that come with BizTalk Server. These tools help you do everything from create the necessary models of your data in an XML-based format to orchestrating the entire business process of processing data. We are going to begin the discussion, however, on how to get started with BizTalk, and then move into specific information about this suite of products.


Getting Started with BizTalk

Part of deploying BizTalk within a given environment is understanding the BizTalk Server suite of tools, how to prepare your system, and how to apply BizTalk within an enterprise (that is, what problems it solves). Over the next few pages we will cover all these topics.

The BizTalk Server Suite of Tools

When you first heard of BizTalk Server you probably thought it was strictly a server product, possibly like Exchange Server, whose sole purpose is to message and coordinate the exchanging of BizTalk-compatible documents. For those of you who have done your homework and researched BizTalk Server, you know this is not true. In addition to the server portion of the application, BizTalk Server comes with several other tools that will help you do everything from creating your data models to describing your messaging workflow.

Although we have already covered two of these tools in Chapter 7 and we will be covering three others in this chapter, we have included the list of applications present in the BizTalk Server product here.

  • BizTalk Administration Tool: This tool is used to manage multiple BizTalk Servers, allowing you to load balance processing between them.
  • BizTalk Messaging Manager: Covered in Chapter 7, this application allows you to set up the proper ports and channels to route documents through your system.
  • BizTalk Document Tracking: Also covered in Chapter 7, this Web-based tool allows you to obtain up-to-date reports and status on messages going through the system.
  • BizTalk Editor: Allows you to create BizTalk-compatible models of the data you plan on exchanging within your system, such as purchase or shipping orders.
  • BizTalk Mapper: Because your system will most likely have to support a variety of different representations of data (that is, you might support a <Company> element and a partner might support <CompanyName>), this application can be used to map one schema to another. Additionally, it contains functoids, which allow you to perform operations, such as mathematical or string manipulation, while transforming data.
  • BizTalk Orchestration Designer: This tool is used to outline and define the entire work flow for documents entering the system, such as how they are processed and by what other applications, if any.

Preparing Your System

Before we dive into the BizTalk Server and the accompanying suite of tools, we want to point out that BizTalk Server has some specific installation requirements. Unlike some of the other Windows .NET products, BizTalk Server has several important dependencies that must be addressed before installing the product. Failure to properly equip your system with these applications will prevent installation. The requirements are

  • Microsoft Windows 2000 Professional, Server, or Advanced Server with Service Pack 1 or higher
  • Microsoft Visio Standard Edition SR-1 (to use Orchestration Designer)
  • Microsoft SQL Server 7 with Service Pack 2 or Microsoft SQL Server 2000

For those of you working to understand BizTalk Server and related applications that do not meet these requirements, Microsoft has made evaluation versions of the necessary software available so you can perform a proper trial. The following locations show you where you can obtain these evaluation copies:

  • Windows 2000 Advanced Server (must order CD): http://microsoft.order-1.com/Win2kEDK
  • Windows 2000 Service Pack 1 (to update your version of Windows 2000 Professional or Server): http://www.microsoft.com/windows2000/downloads/servicepacks/sp1
  • BizTalk Server: http://www.microsoft.com/biztalk/evaluation/trial
  • SQL Server 2000: http://www.microsoft.com/sql/evaluation/trial/2000
  • Visio: http://www.microsoft.com/office/visio/evaluation
Want to avoid downloading hundreds of megabytes to obtain the trials of these products? You can follow the link to order the Microsoft E-Commerce Evaluation Kit from http://www.microsoft.com/biztalk/evaluation/trial. It's less than $10 U.S. This kit contains 120-day trial versions of Windows 2000 Advanced Server, BizTalk Server 2000, Microsoft Commerce Server 2000, SQL Server, and Visio 2000.

Finally, if you simply don't have the time to install and test the software yourself, you can contact Microsoft's Professional Consultation team or a Certified Partner to pay for a pilot. They estimate roughly two weeks to get you installed, configured, and up and running so that you can demo the application in a real-world environment.

Now that we have a full understanding of what is needed to get BizTalk Server off the ground, let's talk about the problems it solves. These will affect your planning and implementation of the product, as well as expose you to some of its abilities.

Applying BizTalk

Unless you are working for a purely dot-com business, you probably have data residing in some older databases, within flat files, or some other kind of resource. If this is the case, and you still have a need to access, update, and utilize this data, you might find yourself faced with performance and flexibility issues. The systems simply might not have the proper methods of accessing and using the data that today's applications or design approaches desire.

Over the next few sections we will discuss the applications of BizTalk and how it can be used to solve these problems. We are going to talk about enterprise application integration, business-to-business integration, and business process automation.

Want to read some real-world case studies of how BizTalk has been used to implement and solve these various instances? Check out http://www.microsoft.com/biztalk/evaluation/casestudies for a list of companies and documents on their implementation.

Enterprise Application Integration

In a technical world where many of the programming youth assume Java, C#, or some scripting language represents all coding methods and that the oldest systems are Windows 98 or Windows NT 4, those who have been around know this is not always the case. Finding COBOL, mainframes, a Virtual Memory System (VMS), running on a Virtual Address extension (VAX), and text-based screens acting as the backbone to entire businesses can be common, especially in large corporations. These legacy systems are still widely used, deployed, and depended on for ongoing business activities. So, how is a company supposed to stay abreast of the latest technology and systems needed to survive if the core and heart behind it is a 20-year-old system?

Above all else, the BizTalk Server suite of applications is designed for ease of integration. It uses industry standards heavily, like XML, SOAP, and the BizTalk Framework. It provides the tools, which we covered earlier, that allow you to create XML representations of your legacy data and coordinate the access and use of that data. This reduces your integration into the legacy system to simply supporting some basic XML features, and BizTalk will take it from that point.

Legacy data, however, is not the only data with which BizTalk can communicate and integrate. In fact, for those of you creating XML-based Web Services such as those covered in this book, you might find BizTalk Server to be the perfect system to handle the processing of messages.

Business-to-Business Integration

Integrating with internal applications is only one type of implementation you will find useful for BizTalk Server. Many businesses are building XML-based systems or interfaces that allow them to automate their communication and exchanges with customer and partner companies. For instance, a retail company might obtain product descriptions and pricing in an XML format. Using BizTalk Server's ability to map one schema to another, you can feel confident that you can design your own schemas to be true to your business, and yet not limit your integration with partners.

The BizTalk Mapper literally allows you to draw lines between elements and attributes of two schemas, with the result being an XSLT style sheet that will transform your partner's data into a format native to your system. This capability allows you to build an XML-based solution within your company, yet give it a long lifetime because supporting new data models involves nothing more than sitting down and drawing out the mapping.

Business Process Automation

Business process automation has moved to the forefront in today's economy. If a company is able to reduce the time and costs associated with a particular task, such as checking inventory, its revenue might not increase, but it has decreased its bottom line costs and thus increased profits. Automation can be an extremely important and cost-saving move for the company, especially when finances are a major concern.

What the BizTalk Orchestration Designer, along with the Messaging Manager, brings to the table is a visually-based method for designing, improving, and automating these business process. With these tools you are able to design entire processes for handling incoming requests or documents and create schedule files that can be processed and tracked.


Schema Creation with BizTalk Editor

The first step in integrating with a legacy application, assuming you have an XML-based architecture in place, is to create the necessary model of the legacy data. This task is accomplished using the BizTalk Editor, show in Figure 15-1, which is capable of creating specifications or schemas that can be adhered to by your applications.

Figure 15-1 The BizTalk Editor.

To create a specification or schema within the Editor, you can either choose to create a new specification from the File menu or schema, or you can import (under the Tools menu) an existing well-formed XML document, a DTD, or an XDR Schema. If you choose to create a new schema, you will be presented with the dialog box shown in Figure 15-2 where you can create a Blank Specification, Electronic Data Interface For Administration, Commerce and Transport (EDIFACT), X12, or XML specification or schema. Furthermore, some common templates under EDIFACT, X12, or XML can help get you started.

Figure 15-2 Various options for creating schemas.

As an example, let's say you have built an issue submission system used by your technical support team. It's a simple system that contains only the customer name, phone, e-mail address, and issue. In Listing 15-1 you can see an example of the information collected.

Listing 15-1 allen-support.xml: A sample submission document of an issue.

<?xml version="1.0" ?>
<support>
  <name>Allen Wyke</name>
  <phone>999.555.1212</phone>
  <email>mspress@microsoft.com</email>
  <issue>Unable to access Web-based email account</issue>
</support>

Now if you use the Import feature under the Tools menu of the Editor and then selecting Well-Formed XML Instance as the type, we can import and build a specification off of this document. The Editor does all the hard work of converting it to the necessary XDR-based format used by the other BizTalk applications. Go ahead and do this and save the file under the name support.xml, which we have included here as Listing 15-2.

If you are interested in controlling other aspects of these specifications, be sure to check the Help files. They will outline how you can specify recurring elements, default values, and so forth—many of the features that schema-based XML dialects allow.

Listing 15-2 support.xml: The specification for our support submission data.

<?xml version="1.0"?>
<!-- Generated by using BizTalk Editor on Thu, Aug 02 2001 10:35:27 PM -->
<!-- Microsoft Corporation (c) 2000 (http://www.microsoft.com) -->
<Schema name="support"
        b:BizTalkServerEditorTool_Version="1.0"
        b:root_reference="support"
        b:standard="XML"
        xmlns="urn:schemas-microsoft-com:xml-data"
        xmlns:b="urn:schemas-microsoft-com:BizTalkServer"
        xmlns:d="urn:schemas-microsoft-com:datatypes">
  <b:SelectionFields/>
  <ElementType name="support" content="eltOnly" model="closed">
    <b:RecordInfo/>
    <element type="name" maxOccurs="*" minOccurs="0"/>
    <element type="phone" maxOccurs="*" minOccurs="0"/>
    <element type="email" maxOccurs="*" minOccurs="0"/>
    <element type="issue" maxOccurs="*" minOccurs="0"/>
  </ElementType>
  <ElementType name="phone" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="name" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="issue" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="email" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
</Schema>

Before we move on, let's assume that you have a partner who will be sending you support cases as well, but that they have a slightly different format. They have broken down the <name> element into first and last names. In Listing 15-3 we have included such an example.

Listing 15-3 partner-support.xml: A sample support submission from a partner.

<?xml version="1.0" ?>
<support>
  <name>
    <first>Allen</first>
    <last>Wyke</last>
  </name>
  <phone>999.555.1212</phone>
  <email>mspress@microsoft.com</email>
  <issue>Unable to access Web-based email account</issue>
</support>

After we import this in the Editor and save it in a file called partner.xml (Listing 15-4), we end up with the following.

Listing 15-4 partner.xml: A specification outlining the partner's format for support submissions.

<?xml version="1.0"?>
<!-- Generated by using BizTalk Editor on Thu, Aug 02 2001 10:45:06 PM -->
<!-- Microsoft Corporation (c) 2000 (http://www.microsoft.com) -->
<Schema name="support"
        b:BizTalkServerEditorTool_Version="1.0"
        b:root_reference="support"
        b:standard="XML"
        xmlns="urn:schemas-microsoft-com:xml-data"
        xmlns:b="urn:schemas-microsoft-com:BizTalkServer"
        xmlns:d="urn:schemas-microsoft-com:datatypes">
  <b:SelectionFields/>
  <ElementType name="support" content="eltOnly" model="closed">
    <b:RecordInfo/>
    <element type="name" maxOccurs="*" minOccurs="0"/>
    <element type="phone" maxOccurs="*" minOccurs="0"/>
    <element type="email" maxOccurs="*" minOccurs="0"/>
    <element type="issue" maxOccurs="*" minOccurs="0"/>
  </ElementType>
  <ElementType name="phone" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="name" content="eltOnly" model="closed">
    <b:RecordInfo/>
    <element type="first" maxOccurs="*" minOccurs="0"/>
    <element type="last" maxOccurs="*" minOccurs="0"/>
  </ElementType>
  <ElementType name="last" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="issue" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="first" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
  <ElementType name="email" content="textOnly" model="closed">
    <b:FieldInfo/>
  </ElementType>
</Schema>

While this quick example certainly does not display all the powers of the BizTalk Editor, it showed you how to perform some basic tasks and how to get started. In the next section we will build on the example and show you how to support the partner's specification by mapping it to yours.


Transformations in BizTalk Mapper

Defining models for the data you will be working with is a huge step toward implementing your integration, but it's only the first piece. Because you might need to support different models and structures that come from partners, customers, and so forth, you will want to map their models to your internally supported ones. The BizTalk Mapper, shown in Figure 15-3, is used for this task. It allows you to import two specifications and draw the necessary lines between them to map from one to the other. The result is an XSLT style sheet that can be used by the server during processing.

Figure 15-3 The BizTalk Mapper.

Now launch BizTalk Mapper and we will step through mapping the partner.xml schema to our support.xml schema to show you how the application works. Once it's open start a new mapping (from the File menu). When the Select Source Specification Type dialog appears, click the Local Files option and browse to the partner.xml file. Once you have it selected you will see the Select Destination Specification Type dialog. Follow the same steps and select the support.xml specification.

Instead of starting at the top and addressing the "name" formatting problem, let's take care of the <phone>, <email>, and <issue> mappings. To map these elements in the partner.xml specification to the support.xml specification, simply drag one to the corresponding other. Figure 15-4 shows us mapping the <issue> elements together after we have already mapped the <phone> and <email> elements.

Figure 15-4 Mapping elements by dragging from one side to the other.
Figure 15-5 Our completed mapping between the partner.xml and support.xml specifications.

To handle the mapping for the customer name we are going to use a functoid to concatenate the <first> and <last> elements together into a single <name>. To apply a functoid you need to open the Functoid Palette from the View menu. When the palette opens you will notice it is already populated with items for basic String, Mathematical, Logical, Date/Time, Conversion, Scientific, Cumulative, Database, and Advanced operations. This option can be extremely powerful because it allows you to, for instance, combine or simply use the results of functions like a database query in your mapping.

For our example you will need to drag the Concatenate String operator to your workspace. Once you have it placed you can draw a line from the <first> element to this functoid and then a line from the <last> element to it. Finally you can connect the functoid to the <name> element on our support.xml destination specification. Once complete, the results, which can be seen in Figure 15-5, are stored in an XML file we will call mapping.xml (Listing 15-5). As you can see, it contains all the information about the specifications, their mapping, even the script needed to perform the functoid Concatenate operation.

Listing 15-5 mapping.xml: Result of mapping our partner.xml specification to our support.xml specification.

<?xml version="1.0"?>
<!-- Generated using BizTalk Mapper on Thu, Aug 02 2001 11:24:20 PM -->
<!-- Microsoft Corporation (c) 2000 (http://www.microsoft.com) -->
<mapsource name="mapping"
           BizTalkServerMapperTool_Version="1.0"
           version="1"
           xrange="100"
           yrange="420">
  <srctree>
    <Schema name="support"
            b:BizTalkServerEditorTool_Version="1.0"
            b:root_reference="support"
            b:standard="XML"
            xmlns="urn:schemas-microsoft-com:xml-data"
            xmlns:b="urn:schemas-microsoft-com:BizTalkServer"
            xmlns:d="urn:schemas-microsoft-com:datatypes">
      <b:SelectionFields/>
      <ElementType name="support" content="eltOnly" model="closed">
        <b:RecordInfo/>
        <element type="name" maxOccurs="*" minOccurs="0"/>
        <element type="phone" maxOccurs="*" minOccurs="0"/>
        <element type="email" maxOccurs="*" minOccurs="0"/>
        <element type="issue" maxOccurs="*" minOccurs="0"/>
      </ElementType>
      <ElementType name="phone" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="name" content="eltOnly" model="closed">
        <b:RecordInfo/>
        <element type="first" maxOccurs="*" minOccurs="0"/>
        <element type="last" maxOccurs="*" minOccurs="0"/>
      </ElementType>
      <ElementType name="last" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="issue" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="first" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="email" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
    </Schema>
  </srctree>
  <Values>
    <TestValues/>
    <ConstantValues/>
  </Values>
  <sinktree>
    <Schema name="support"
            b:BizTalkServerEditorTool_Version="1.0"
            b:root_reference="support"
            b:standard="XML"
            xmlns="urn:schemas-microsoft-com:xml-data"
            xmlns:b="urn:schemas-microsoft-com:BizTalkServer"
            xmlns:d="urn:schemas-microsoft-com:datatypes">
      <b:SelectionFields/>
      <ElementType name="support" content="eltOnly" model="closed">
        <b:RecordInfo/>
        <element type="name" maxOccurs="*" minOccurs="0"/>
        <element type="phone" maxOccurs="*" minOccurs="0"/>
        <element type="email" maxOccurs="*" minOccurs="0"/>
        <element type="issue" maxOccurs="*" minOccurs="0"/>
      </ElementType>
      <ElementType name="phone" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="name" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="issue" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
      <ElementType name="email" content="textOnly" model="closed">
        <b:FieldInfo/>
      </ElementType>
    </Schema>
  </sinktree>
  <links>
    <link linkid="1" linkfrom="1" linkto="/support/name"/>
    <link linkid="2" linkfrom="/support/name/last" linkto="1"/>
    <link linkid="3" linkfrom="/support/name/first" linkto="1"/>
    <link linkid="4" linkfrom="/support/issue" linkto="/support/issue"/>
    <link linkid="5" linkfrom="/support/email" linkto="/support/email"/>
    <link linkid="6" linkfrom="/support/phone" linkto="/support/phone"/>
  </links>
  <functions>
    <function functionid="1" xcell="56" ycell="212" funcfuncid="107"
funcversion="1" isscripter="no">
      <inputparams>
        <param type="link" value="3"/>
        <param type="link" value="2"/>
      </inputparams>
    </function>
  </functions>
  <CompiledXSL>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                    xmlns:var="urn:var"
                    xmlns:user="urn:user"
                    exclude-result-prefixes="msxsl var user"
                    version="1.0">
      <xsl:output method="xml" omit-xml-declaration="yes"/>
      <xsl:template match="/">
        <xsl:apply-templates select="support"/>
      </xsl:template>
      <xsl:template match="support">
        <support>
          <xsl:variable name="var:v1"
               select="user:fctstringconcat2(string(name/first/text()),string(name/last/text()))"/>
          <name>
            <xsl:value-of select="$var:v1"/>
          </name>
          <!-- Connection from source node "phone" to destination node "phone" -->
          <phone>
            <xsl:value-of select="phone/text()"/>
          </phone>
          <!-- Connection from source node "email" to destination node "email" -->
          <email>
            <xsl:value-of select="email/text()"/>
          </email>
          <!-- Connection from source node "issue" to destination node "issue" -->
          <issue>
            <xsl:value-of select="issue/text()"/>
          </issue>
        </support>
      </xsl:template>
      <msxsl:script language="VBScript"
                    implements-prefix="user">
        <![CDATA[
          Function FctStringConcat2( p_strParm0, p_strParm1 )
            FctStringConcat2 = p_strParm0 + p_strParm1
          End Function
        ]]>
      </msxsl:script>
    </xsl:stylesheet>
  </CompiledXSL>
</mapsource>

The BizTalk Mapper can be an extremely useful and time-saving application when it comes to supporting new document formats in any BizTalk system you might be building. Be sure to check out the Help files for more information on the many features it offers.


Process Design with BizTalk Orchestration Designer

While many XML-based messaging servers offer the ability to transport and track XML messages in and out of systems, no application that can create and describe the processing flow a document must go through from start to finish exists. Enter BizTalk Orchestration Designer. This application, shown in Figure 15-6, allows you to drag and drop the necessary flowchart items to define a business process and then connect them to the implementation components used in processing the data.

Figure 15-6 The BizTalk Orchestration Designer.

Figure 15-6 shows you a visual representation of how these items are tied together, but as technologists we are sure you want to know and understand what is happening in the background. Behind the scenes, and after you have described your business process and its relationship to processing components, the Orchestration Designer creates an XLANG schedule. This schedule, which is saved with an .skx extension, is run by the XLANG Scheduler Engine, which controls the activation, execution, dehydration, and rehydration of the XLANG schedule. In other words, it handles the execution of the schedule details.

The functionality of the Orchestration Designer can be broken down into three areas. These are as follows:

  • Defining workflow
  • Referencing processing applications
  • Attaching steps to processing

To help you understand these items we are going to step through parts of an example, which will also familiarize you with the tool. This example will use the Orchestration Designer to route our technical support issue submission through our system.

Defining Work Flow

The first step we will take in creating this example is to define the business process workflow. For those of you familiar with Visio, the Orchestration Designer functions in much the same manner. Most of the time you drag components, shapes, and so forth, to your workspace and access properties of these items to define their functions. After you have launched the BizTalk Orchestration Designer, step through the following items to define the business workflow.

For all of these functions you will drag items from the Flowchart stencil on the leftmost side of the application to the left of the Separator bar.
  1. Drag an Action to a position below the Begin shape.
  2. Double-click the Action, and in the Name field enter Receive Email.
  3. Next drag a Decision shape and place it below the Receive Email action.
  4. Right-click the Decision shape and click Add Rule. In the dialog box that appears, type Phone and click OK. Repeat the same steps and add a second rule named Email.
  5. Add a second Action shape to the right of the Decision box and name it Phone Customer.
  6. Add a third Action shape below and to the left of the Phone Customer and name it Send Email.
  7. Below the Decision box, add a forth Action and name it Push To Manager.
  8. Finally we need to place three End items. Place one below the Push To Manager action, another below the Send Email action, and the last one below the Phone Customer action.

Now that all of our process descriptions are on the page, we need to connect these items. To connect them you need to follow these steps:

  1. Select an item by clicking on it so that a green outline shows.
  2. Select a control handle, which is represented by an x, of the first step in a process and drag it to one of the control handles of the action to which you want to connect it.

Connecting is this simple. In our example you will need to make the following eight connections:

  • Begin to Receive Mail
  • Receive Mail to Decision
  • Phone to Phone Customer
  • Phone Customer to End (one we placed below this action)
  • Email to Send Email
  • Send Email Customer to End (one we placed below this action)
  • Else to Push To Manager
  • Push To Manager to End (one we placed below this action)

At this point your screen should look like Figure 15-7. Now that this portion of our example is complete, save it in a file named Support.skv on the C drive. We will now begin adding our processing applications to the Implementation side of the workspace.

Figure 15-7 Our example after designing the workflow.

Referencing Processing Applications

One of the great qualities of the Orchestration Designer is its ability to include definitions and descriptions of the processing applications. For instance, we might want to use a component to send the customer with a support issue an e-mail with the resolution. Maybe we want to pass the document to a script first and perform other processing before a phone call is made to the customer. Using the Orchestration Designer we are able to drag category components to our workspace, select the scripts, COM objects, message queues, and BizTalk messaging applications that will be used to process the data.

In our example we are going to add a few script components. The point of this exercise is to expose you to some of the terminology and screens you should expect to see when defining the processing applications. For instance, you will create ports that act as the conduit between the business process and the applications. Furthermore, you will be able to see how the Orchestration Designer represents these connections.

The following steps will show you how to add Script Components out of the Implementation stencil.

  1. Drag a Script Component to the left side of the workspace, opposite the Phone Customer action. This will open the Script Component Binding Wizard dialog, where you will enter ProcessPhone as the name of the Port. Click Next when you have entered this name.
  2. Accept the default setting of Static on the second dialog and click Next.
  3. Now we have to specify a Script file. We are going to use one that was already built and is contained in a file called MsgBoxes.wsc (Listing 15-6). This should be located in the \Program Files\Microsoft BizTalk Server\ SDK\XLANG Samples\Loop\LoopApp directory if you installed the application in its default location. Here are the contents of the file in case you did not:

    Listing 15-6 MsgBoxes.wsc: A script used to display our dialog boxes.

    <?xml version="1.0"?>
<component>
<?component error="true" debug="true"?>
<registration
    description="MsgBoxes"
    progid="MsgBoxes.WSC"
    version="1.00"
    classid="{df0c82d7-f193-48f0-bfcd-eebc818bf2d3}"
>
</registration>
<public>
    <method name="ShowMsgBox">
        <PARAMETER name="sMsg"/>
        <PARAMETER name="sTitle"/>
    </method>
    <method name="AskYesNo">
        <PARAMETER name="sMsg"/>
        <PARAMETER name="sTitle"/>
    </method>
</public>
<script language="VBScript">
<![CDATA[
function ShowMsgBox(sMsg, sTitle)
    MsgBox CStr(sMsg), vbOKOnly, CStr(sTitle)
    ShowMsgBox = ""
end function
function AskYesNo(sMsg, sTitle)
    AskYesNo = MsgBox(CStr(sMsg), vbYesNo, CStr(sTitle))
end function
]]>
</script>
</component>
  4. After you've clicked Next you will select the instantiation option for the component. Leave this setting at Use the ProgID MsgBoxes.WSC. Click Next when you are finished.
  5. On the Method Information dialog, select only the ShowMsgBox method and click Next.
  6. On the final screen, Advanced Port Properties, we will accept all the default settings and click Finish.
  7. Now repeat these steps to handle the processing of the Send Email and Push To Manager actions. All you need to do is change the name of the Port in Step #1 to ProcessEmail for one instance, and ProcessManager in the second.
Want to know more about the other Implementation components? Be sure to check the BizTalk Help files—they contain more information, as well as other tutorials.

Once you've completed these steps, the right side of your workspace should look like Figure 15-8.

Figure 15-8 The results after connecting to processing scripts.

Before we move on, let's include a processing component that will be responsible for starting the entire process. This component, which will be of the Message Queuing type, will be what feeds the Receive Email action we defined on the business process setup. Here are the steps for adding this component.

  1. Drag a Message Queuing component to the left side of the workspace, opposite the Receive Email action. This will open the Message Queuing Binding Wizard dialog, where you will enter IncomingEmail as the name of the Port. Click Next when you have entered this name.
  2. Accept the default setting of Static on the second dialog and click Next.
  3. Now we need to include the queue information. For this example we will create a new queue for every instance. Simply accept the default setting and hit Next. (If you've forgotten how, check back in Chapter 7 for message queuing details.)
  4. On the final screen, Advanced Port Properties, we will accept the default settings, so just click Finish.

At this point we have all the pieces in place—we just need to properly associate them and apply any needed functionality. In the next section, we will attach our business actions to our processing scripts and put the finishing touches on our example.

Attaching Steps to Processing

The final step in our example of using the Orchestration Designer application is to connect the business steps to the processing applications (scripts in our case). This step will not only involve defining the processing connections, but we will also add information in our Decision item to determine which connection is used.

In the same manner we used to connect the items together in the business process, we will drag control handles from the appropriate actions to the corresponding script component port. The following outlines how to complete this process.

Remember that the "port" is defined by the name we gave it. You can see these ports in the middle of your workspace.
  1. Grab the rightmost control handle for the Receive Email action and drag it to the leftmost handle on the IncomingEmail port. This will cause the XML Communication Wizard to appear.
  2. Select the Receive option to signify that the XLANG Scheduler Engine will be receiving the message. Click Next after you have entered this information.
  3. On the following Message Information dialog, enter SupportMessage as the name of the message, and click Next.
  4. Accept the default setting of Receiving XML Messages From The Queue on the XML Translation Information dialog and click Next.
  5. On the Message Type Information dialog, enter <support> as the Message Type and click Next.
  6. On the following screen browse to our mapping.xml specification and click Finish to finalize the association.
  7. Next grab the rightmost control handle for the Phone Customer action and drag it to the leftmost handle on the ProcessPhone port. This will cause the Method Communication Wizard to appear.
  8. On the initial dialog accept the default of initiating a synchronous method call and click Next.
  9. Click Next again on the Message Information dialog. This will present you with the third and final dialog that outlines the details of the ShowMsgBox method. Click Finish when you are done.
  10. Repeat steps 7—9 for the connection between the Send Mail action and the ProcessEmail port.
  11. Repeat steps 7—9 for the connection between the Push To Manager action and the ProcessManager port.

Once you have completed these steps, your workspace should look like Figure 15-9.

Figure 15-9 The workspace after completing the process-to-port connections.

The next step in our example is to write the expressions to evaluate our document so we can properly process the data. Follow these steps to add in the necessary expressions:

  1. Right click on the Decision item and select Properties. This will launch a dialog box displaying our Phone and Email rules.
  2. Select the Phone rule and click the Edit button.
  3. In the Rule Properties dialog place your cursor in the Script Expression textarea. Now type SupportMessage.Phone > 1. Click Ok when you are finished.
  4. Repeat Steps 2—3 for the Email rule and enter SupportMessage.Email > 1 for the expression.
What does this mean? Typing SupportMessage.Phone signifies that we are looking for the presence of a phone number in the document submitted. If this evaluates to TRUE, we will go through the ProcessPhone port and run the proper script.

We are now finished. Save your file so you do not lose any changes. At this point you should be able to combine what you've learned here with what we covered in Chapter 7 to build a complete BizTalk solution, or at least a simple one.

If you are still interested in the BizTalk Framework and Server product, check out http://mspress.microsoft.com. You will find several Microsoft Press titles on the subject.


Conclusion

As you have seen, the power of BizTalk Server is truly amazing. The ability of an application to both manage the messaging between servers necessary for proper integration and also provide the toolset need to implement and orchestrate the entire process makes BizTalk Server a must-have application. When the original BizTalk Framework was developed, Microsoft obviously considered its potential power, and they have leveraged it within this server product.

In Chapter 16 we will go over Windows .NET and what it really means. Throughout the course of this book we have touched on, discussed, and used many of the .NET features and we have built applications based on .NET, but we have not pulled all of this information together into a full explanation. Although .NET is not the focus of the book, its usage of XML-based standards warrants additional coverage.


.NET Development

By now you should have little doubt that XML truly forms a core technology substrate in Microsoft's .NET Framework. Everywhere in Windows .NET, from metadata to ASP.NET to XML Web services, XML is used as the native data representation format. This chapter starts by exploring the other major use of XML in .NET, data access, to show you how Microsoft has effectively merged the relational and hierarchical DOM access models under a single, unified, architecture.

Next we dig deeper into the XML object model of the .NET Framework to look at reading and writing XML documents. Finally we allow ourselves to stray slightly off course from the main focus of this book to take a look at some of the popular programming languages that can be used to build .NET applications.


Data Access and XML

Prior to Windows .NET, OLEDB and ActiveX Data Objects (ADO) were the primary APIs for data access in Microsoft Windows. With the advent of .NET, Microsoft introduced a brand new data access object model, called ADO.NET, which is leaner and meaner than previous data access object models and provides functionalities tailored to excel in a multitier, distributed Web environment.

All three data access object models, OLEDB, ADO, and ADO.NET, provide varying levels of support for XML. Let's first take a look at OLEDB and ADO and their support for XML.

OLEDB and ADO

Both OLEDB and ADO are based on COM technologies. The OLEDB provides a low-level and highly efficient method of accessing and manipulating relational data, while ADO, which is built on top of OLEDB, provides an easier means of data access. XML support in ADO is provided through Recordset Persistence and is implemented by the Microsoft OLE DB Persistence Provider. This provider can take an ADO Recordset object, generate an XML document and related schema information, and save all this to a stream or file. Similarly, this provider can generate a read-only and forward-only Recordset object from an ADO-generated XML file or stream.

Listing 16-1 demonstrates how you can persist a Recordset object as an XML file.

Listing 16-1 ADORecordset2XML.cs: Persisting an ADO Recordset object to an XML file.

using System;
using System.Data;
using System.Data.SQL;
public class ADORecordset2XML
{
  public static void Main( String[] args )
  {
    try
    {
      String connString = "provider=SQLOLEDB;" +
                          "initial catalog=pubs;" +
                          "server=localhost;" +
                          "uid=sa;" +
                          "pwd=";
      String queryString = "select * from titles";
      /* Instantiate a Recordet object */
      ADODB.Recordset rs = new ADODB.Recordset();
      /* Connect to the database and execute a SQL query */
      rs.Open( queryString, connString,
               ADODB.CursorTypeEnum.adOpenStatic,
               ADODB.LockTypeEnum.adLockReadOnly, 0 );
      /* Export Recordset to "titles.xml" file in XML format. */
      rs.Save( "titles.xml", ADODB.PersistFormatEnum.adPersistXML );
      /* close the Recordset object */
      rs.Close();
    }
    catch ( Exception e )
    {
      Console.WriteLine( "Exception: {0}", e.ToString() );
    }
  }
}

In ADORecordset2XML.cs we first open a connection to the SQL Server PUBS database and then populate a Recordset object with all records from the TITLES table. To persist this Recordset object, we simply call the Save( ) method and specify the filename to which to write the data and the output format, which in this case is XML.

To compile ADORecordset2XML.cs, open a console window and type in the following command line:

csc /r:System.dll /r:System.Data.dll /r:ADODB.dll ADORecordset2XML.cs

After you run the resultant ADORecordset2XML.exe program you should see a new titles.xml file in the current directory. This is the XML-persistent form of the Recordset object you created in ADORecordset2XML.cs.

Listing 16-2 shows how you can use ADO to load the titles.xml file you generated with ADORecordset2XML.cs into a new Recordset object.

Listing 16-2 XML2ADORecordset.cs: Recreating the Recordset object from the titles.xml file.

using System;
using System.Data;
using System.Data.SQL;
public class XML2ADORecordset
{
  public static void Main( String[] args )
  {
    try
    {
      String connString = "provider=SQLOLEDB;" +
                          "initial catalog=pubs;" +
                          "server=localhost;" +
                          "uid=sa;" +
                          "pwd=";
      Int32 adCmdFile = 256;
      /* Instantiate Recordset object */
      ADODB.Recordset rs = new ADODB.Recordset();
      /* Open the titles.XML file into Recordset object */
      rs.Open( "titles.xml", connString,
               ADODB.CursorTypeEnum.adOpenForwardOnly,
               ADODB.LockTypeEnum.adLockReadOnly,
               adCmdFile );
      /* Display the value of the price field for the first record */
      rs.MoveFirst();
      Console.WriteLine( rs.Fields[ "price" ].Value );
      /* close the Recordset object */
      rs.Close();
    }
    catch ( Exception e )
    {
      Console.WriteLine( "Exception: {0}", e.ToString() );
    }
  }
}

In XML2ADORecordset.cs you can see that, to open a persisted Recordset file, you can use the normal Recordset Open( ) method and pass in the name of the persisted file. In the example, once we have loaded and recreated the Recordset object, we generate the value of the price field of the first record.

Use the following command to compile XML2ADORecordset.cs:

csc /r:System.dll /r:System.Data.dll /r:ADODB.dll XML2ADORecordset.cs

When you run the resultant XML2ADORecordset.exe program, you will see the value of the price field displayed.

XML support in ADO is limited to say the least. You can only persist and reconstruct Recordset objects to and from XML files and streams. Once persisted, the XML file remains separate from the original Recordset object, and any changes you make to one will not be reflected in the other automatically.

ADO.NET, on the other hand, provides a much tighter relationship with XML, so much so that the two are actually based off a common architecture in the .NET Framework. Let's turn our attention to this exciting new technology.

ADO.NET

ADO.NET is touted as the successor to ADO and is based heavily on XML. The core focus of ADO.NET is to provide an API that facilitates the creation of distributed, scalable, and interoperable applications that share data in a disconnected, stateless fashion.

ADO.NET consist of two parts: the DataSet class and Managed Providers. The DataSet class provides the methods and properties for accessing and manipulating relational data. Managed Providers represent the underlying data stores such as a Microsoft SQL Server database.

Table 16-1 shows a list of the namespaces made available with ADO.NET.

Table 16-1 Namespaces Made Available with ADO.NET
Namespace | Description
System.Data | Contains the DataSet class and other basic classes.
System.Data.ADO | Contains the ADO Managed Provider classes.
System.Data.SQL | Contains SQL Server Managed Provider classes.
System.Data.SQLTypes | Contains classes for native types within SQL Server.

The DataSet Class

The DataSet class is central to ADO.NET and represents the primary encapsulation for data access and manipulation in the .NET Framework. DataSet is a disconnected and in-memory view of a database. You should appreciate that a DataSet knows nothing about SQL or even DBMS's. You can have a DataSet dynamically created and populated with data without a single byte of communication made with a DBMS. This decoupling between data and the underlying data source is the core enabler for the ADO.NET disconnected mode of operations.

Internally you can think of a DataSet as an XML document containing one or more pieces of separate or related tabular XML elements. In fact, .NET provides a unified programming model for accessing data represented as both XML data and relational data. You can start with a DataSet and work with the data following a non-sequential, hierarchical path driven by the XML DOM. Equally, you can work with XML documents in a row-after-row fashion through a DataSet using a relational model.

In .NET Framework the XmlDataDocument class provides this integration between the relational and XML models.

The XmlDataDocument Class

The XmlDataDocument class is extended from the XmlDocument class, which itself is the primary encapsulation in the .NET Framework for an XML document. XmlDataDocument provides the extra capability for manipulating relational data. Like all other XML-related classes, XmlDataDocument resides in the System.Xml namespace.

Once a relationship between a DataSet and XmlDataDocument is established, synchronization of the underlying data is automatic. Therefore, adding a record in the DataSet, for example, always creates a corresponding node in the XmlDataDocument. Conversely, adding a node in the XmlDataDocument also creates a new record in the DataSet.

Depending on the source of your data, you can instantiate an XmlDataDocument and associate a DataSet with it in two ways. The first method is that if your source data is in a DataSet, you instantiate a new XmlDataDocument, passing the DataSet in the constructor. Your DataSet is now attached to the XmlDataDocument, and you can use the exposed methods and properties to access and manipulate the relational data using the DOM.

Every rule has exceptions. A newly added node in the XmlDataDocument will result in a new record in the DataSet only if the new node is recognized as corresponding to a row in the DataSet. This behavior allows the XML document to contain elements that do not correspond to relational records.

In Listing 16-3 we show you a sample C# console program that retrieves data from an SQL Server database and uses XmlDataDocument to manipulate the data.

Listing 16-3 DataSet2XmlDataDocument.cs: Manipulating relational data using the DOM.

using System;
using System.IO;
using System.Data;
using System.Data.SQL;
using System.Xml;
public class DataSet2XmlDataDocument
{
    public static void Main( String[] args )
    {
        SQLConnection conn = null;
        try
        {
            /* connect to the SQL Server database */
            conn = new SQLConnection( "server=
localhost;uid=sa;pwd=;database=pubs" );
            /* execute SQL query */
            SQLDataSetCommand cmd = new SQLDataSetCommand( "select * from Titles", conn );
            /* create a DataSet and populate it with the records returned from
               result of the above SQL query */
            DataSet ds = new DataSet();
            cmd.FillDataSet( ds, "Titles" );
            /* associate this DataSet with an XmlDataDocument */
            XmlDataDocument doc = new XmlDataDocument( ds );
            /* you can now process this doc just like any
DataDocument. For example,
               the following uses DataDocumentNavigator to select the first Titles node
               and modifies the value. */
            DataDocumentNavigator nav = new DataDocumentNavigator
( doc );
            nav.Select( "//NewDataSet/Titles[1]/title_id" );
            if ( nav.MoveToNextSelected() )
            {
                Console.WriteLine( "Original=" + nav.InnerText );
            }
            else
            {
                Console.WriteLine( "Unexpected error: Node not found" );
            }
            nav.InnerText = "BU1030";  /* modify the value of the title_id node */
            /* XmlDataDocument auto synchronizes the above change with the associated
               DataSet. */
            Console.WriteLine( "Modified=" + ds.Tables[ "Titles" ].Rows[ 0 ][ "title_id" ] );
        }
        finally
        {
            if ( conn != null && conn.State == DBObjectState.Open )
            {
                conn.Close();
            }
        }
    }
}

Use the following console command to compile DataSet2XmlDataDocument.cs:

csc /r:System.dll /r:System.Data.dll /r:System.Xml.dll DataSet2XmlDataDocument.cs

When you execute DataSet2XmlDataDocument.exe you should get an output in your console window similar to the following:

Original=BU1032
Modified=BU1030

You can see that we have successfully used the DOM to modify the data in a DataSet.

If your source data is in an XmlDocument or even an XmlDataDocument, you can use its DataSet member property to access and manipulate the data using a relational model. In Listing 16-4 we have a C# console program that uses an associated DataSet to change the data originally stored in an XmlDataDocument.

Listing 16-4 XmlDataDocument2DataSet.cs: Manipulating an XML document using a DataSet.

using System;
using System.IO;
using System.Data;
using System.Xml;
public class Xml2DataSet
{
  public static void Main()
  {
    try
    {
      /* instantiate a XmlDataDocument */
      XmlDataDocument doc = new XmlDataDocument();
      /* load the schema */
      doc.DataSet.ReadXmlSchema( "book.xsd" );
      /* now load the XML document */
      doc.Load( "book.xml" );
      /* print original price value of first Title node */
      DataDocumentNavigator nav = new DataDocumentNavigator( doc );
      nav.Select( "//Book/Titles[1]/price" );
      if ( nav.MoveToNextSelected() )
      {
        Console.WriteLine( "Original=" + nav.InnerText );
      }
      /* update a price using the associated DataSet */
      DataTable books = doc.DataSet.Tables[ "Titles" ];
      books.Rows[ 0 ][ "price" ] = "12.99";
      /* display modified price */
      nav.Select( "//Book/Titles[1]/price" );
      if ( nav.MoveToNextSelected() )
      {
        Console.WriteLine( "Modified=" + nav.InnerText );
      }
    }
    catch (Exception e)
    {
      Console.WriteLine ("Exception: {0}", e.ToString());
    }
  }
}

XmlDataDocument2DataSet.cs is the example of an XML document we will use in Listing 16-5.

Listing 16-5 book.xml: An XML document used in XmlDataDocument2DataSet.cs.

<Book>
  <Titles>
    <title_id>BU1032</title_id>
    <title>The Busy Executive's Database Guide</title>
    <type>business</type>
    <pub_id>1389</pub_id>
    <price>19.99</price>
    <advance>5000</advance>
    <royalty>10</royalty>
    <ytd_sales>4095</ytd_sales>
    <notes>An overview of available database systems with
emphasis on common business applications. Illustrated.</notes>
    <pubdate>1991-06-11T16:00:00</pubdate>
  </Titles>
  <Titles>
    <title_id>BU1111</title_id>
    <title>Cooking with Computers: Surreptitious Balance Sheets
</title>
    <type>business</type>
    <pub_id>1389</pub_id>
    <price>11.95</price>
    <advance>5000</advance>
    <royalty>10</royalty>
    <ytd_sales>3876</ytd_sales>
    <notes>Helpful hints on how to use your
electronic resources to the best advantage.</notes>
    <pubdate>1991-06-08T16:00:00</pubdate>
  </Titles>
  <Titles>
    <title_id>BU2075</title_id>
    <title>You Can Combat Computer Stress!</title>
    <type>business</type>
    <pub_id>0736</pub_id>
    <price>2.99</price>
    <advance>10125</advance>
    <royalty>24</royalty>
    <ytd_sales>18722</ytd_sales>
    <notes>The latest medical and psychological techniques for living
with the electronic office. Easy-to-understand explanations.</notes>
    <pubdate>1991-06-29T16:00:00</pubdate>
  </Titles>
</Book>

We have also included the governing schema for our book.xml document in Listing 16-6.

Listing 16-6 book.xsd: The XML schema for book.xml.

<xsd:schema id="Book" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata=
"urn:schemas-microsoft-com:xml-msdata">
  <xsd:element name="Titles">
    <xsd:complexType content="elementOnly">
      <xsd:all>
        <xsd:element name="title_id" type="xsd:string"/>
        <xsd:element name="title" type="xsd:string"/>
        <xsd:element name="type" type="xsd:string"/>
        <xsd:element name="pub_id" minOccurs="0" type="xsd:string"/>
        <xsd:element name="price" minOccurs="0" type="xsd:decimal"/>
        <xsd:element name="advance" minOccurs="0" type="xsd:decimal"/>
        <xsd:element name="royalty" minOccurs="0" type="xsd:int"/>
        <xsd:element name="ytd_sales" minOccurs="0" type="xsd:int"/>
        <xsd:element name="notes" minOccurs="0" type="xsd:string"/>
        <xsd:element name="pubdate" type="xsd:timeInstant"/>
      </xsd:all>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="NewDataSet" msdata:IsDataSet="True">
    <xsd:complexType>
      <xsd:choice maxOccurs="unbounded">
        <xsd:element ref="Titles"/>
      </xsd:choice>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

XmlDataDocument2DataSet.cs uses a DataSet to change the price node value of the first Titles node in book.xml from 19.99 to 12.99. The modification is automatically reflected in the doc XmlDataDocument object.

To compile XmlDataDocument2DataSet.cs, run the following command line in a console window:

csc /r:System.dll /r:System.Data.dll /r:System.Xml.dll XmlDataDocument2DataSet.cs

When you run the resultant XmlDataDocument2DataSet.exe in a console window, you should see an output similar to the following:

Original=19.99
Modified=12.99

We have successfully modified the price from 19.99 to 12.99 using the associated DataSet.


Parsing and Producing XML Documents

The XmlReader and XmlWriter abstract classes are at the heart of the XML object model in the .NET Framework. XmlReader provides the API for reading XML documents, while XmlWriter provides the complementary API for producing W3C's standards-compliant XML documents. In designing these classes, Microsoft borrows concepts from both the DOM and SAX. In the end, these classes use neither of those models, but are a compromise between the two.

Like SAX, XmlReader and XmlWriter use a streaming model to read the streams of data that form an XML document one piece at a time, and can even skip pieces of no interest. At the same time, like DOM, the API is based on a more developer-friendly pull model. This innovative stream-based, pull-model API provides developers with means for accessing and producing XML documents that are efficient, powerful, and easy to use.

Because both XmlReader and XmlWriter are abstract classes, you cannot instantiate and work directly with them. For this purpose, .NET provides a few concrete implementations of these classes. For XmlReader, these are the XmlTextReader, XmlNodeReader, and XslReader concrete classes. For XmlWriter, we have XmlTextWriter and XmlNodeWriter.

We will first look at how we can use the XmlReader to read and process XML documents.

XmlReader

The XmlReader abstract class provides a fast, read-only, and forward-only cursor for reading XML documents. Using XmlReader is much like using the DOM, where you read and work with one node at a time. The following snippet shows a C# method that traverses an XML document and displays the names of all the elements in the document.

public void DisplayElements( XmlReader reader )
{
  /* read the next node in document order */
  while ( reader.Read() )
  {
    /* if this is an element node, display its name */
    Console.WriteLine( reader.Name );
  }
}

XmlReader has an associated cursor that defines the notion of a current node in the document stream. Methods are provided that allow the user to traverse the document one node at a time, moving the current node along the way. XmlReader also provides several methods for inspecting the type and value of the current node.

Reading Nodes

The Read( ) method is the fundamental method in XmlReader for moving the cursor in an XML document. Every time you call Read( ), XmlReader moves the cursor to the next node in document order until it reaches the end of the stream, in which case it returns a false value. For example, running the previous sample DisplayElements( ) method against the sample1.xml file in Listing 16-7 produces the following output:

persons
person
firstname
lastname
person
firstname
lastname
person

Listing 16-7 sample1.xml: A sample XML document.

<persons>
  <person>
    <firstname>Albert</firstname>
    <lastname>Einstein</lastname>
  </person>
  <person>
    <firstname>Niels</firstname>
    <lastname>Bohr</lastname>
  </person>
</persons>

Once you have landed at a certain node through the Read( ) method or any other methods provided by the XmlReader for moving the cursor, you can inspect its contextual information or value. For example, the NodeType property returns a type identifier similar to those used by the DOM. Another property, Value, allows you to access a node's value. XmlReader also provides methods for reading typed text values (for example, ReadInt16, ReadDouble, ReadString, and so forth).

The following snippet is a C# method that uses the various properties exposed by XmlReader to display the textual representation of the current node.

public void DisplayNode( XmlReader node )
{
  switch ( node.NodeType )
  {
    case XmlNodeType.Element:
      Console.Write( "<" + node.Name + ">" );
      break;
    case XmlNodeType.Text:
      Console.Write( node.Value );
      break;
    case XmlNodeType.CDATA:
      Console.Write( node.Value );
      break;
    case XmlNodeType.ProcessingInstruction:
      Console.Write( "<?" + node.Name + " " + node.Value + "?>" );
      break;
    case XmlNodeType.Comment:
      Console.Write( "<!--" + node.Value + "-->" );
      break;
    case XmlNodeType.Document:
      Console.Write( "<?xml version='1.0'?>" );
      break;
    case XmlNodeType.Whitespace:
      Console.Write( node.Value );
      break;
    case XmlNodeType.SignificantWhitespace:
      Console.Write( node.Value );
      break;
    case XmlNodeType.EndTag:
      Console.Write( "</" + node.Name + ">" );
      break;
  }
}

Reading Attributes

Because attributes are regarded as part of a documen's hierarchical structure, Read( ) will not encounter attribute nodes. To access attributes for the current element you can use the GetAttribute( ) method. With GetAttribute( ) you can look up a specific attribute either by name or index. The following code snippet shows one way to iterate through and display all attributes in the current node:

for ( int = 0; i < node.AttributeCount; i++ )
{
  Console.WriteLine( node.GetAttribute( i ) );
}

XmlReader also provides methods to let you traverse the attributes using the current node cursor. The MoveToAttribute( ), MoveToFirstAttribute( ), and MoveToNextAttribute( ) methods can be used to move sideways through the attributes attached to the current node. The following code snippet displays the names and values of all the attributes in the current node:

while ( node.MoveToNextAttribute() )
{
  Console.Write( " " + node.Name + "=\"" + node.Value + "\"" );
}

Notice that, unlike GetAttribute( ), you can retrieve both the name and the value of an attribute using this method.

A Sample XML Reader

ReadXML.cs, in Listing 16-8, shows a C# console program that uses all the methods and properties described in this section to display the complete textual representation of the book.xml XML file.

Listing 16-8 ReadXML.cs: A C# console using the discussed methods and properties.

using System;
using System.IO;
using System.Xml;
public class ReadXML
{
  public void DisplayNode( XmlReader node )
  {
    switch ( node.NodeType )
    {
      case XmlNodeType.Element:
        Console.Write( "<" + node.Name );
        while ( node.MoveToNextAttribute() )
        {
          Console.Write( " " + node.Name + "=\"" + node.Value + "\"" );
        }
        Console.Write(">");
        break;
      case XmlNodeType.Text:
        Console.Write( node.Value );
        break;
      case XmlNodeType.CDATA:
        Console.Write( node.Value );
        break;
      case XmlNodeType.ProcessingInstruction:
        Console.Write( "<?" + node.Name + " " + node.Value + "?>" );
        break;
      case XmlNodeType.Comment:
        Console.Write( "<!--" + node.Value + "-->" );
        break;
      case XmlNodeType.Document:
        Console.Write( "<?xml version='1.0'?>" );
        break;
      case XmlNodeType.Whitespace:
        Console.Write( node.Value );
        break;
      case XmlNodeType.SignificantWhitespace:
        Console.Write( node.Value );
        break;
      case XmlNodeType.EndTag:
        Console.Write( "</" + node.Name + ">" );
        break;
    }
  }
  public void DisplayElements( XmlReader reader )
  {
    /* read the next node in document order */
    while ( reader.Read() )
    {
      DisplayNode( reader );
    }
  }
  public static void Main( String[] args )
  {
    XmlTextReader reader = new XmlTextReader( "book.xml" );
    ReadXML tr = new ReadXML();
    tr.DisplayElements( reader );
  }
}

Note that we have used the XmlTextReader concrete implementation of XmlReader in ReadXML.cs.

To compile ReadXML.cs, open a console window and type in the following command line:

csc /r:System.dll /r:System.Xml.dll ReadXML.cs

Running the resultant ReadXML.exe program displays the content of book.xml on the console.

Validation

One of the features the XmlTextReader provides is validation. XmlTextReader supports validation against DTDs, XDR, and XSD schemas. Validation is off by default, and to turn it on you must provide a ValidationHandler and the Validation property must be set to either DTD or Schema, depending on the validation method required. With validation turned on, the ValidationHandler event handler is called whenever a validation error occurs. A validation error is any error listed in the W3C as "Validity Constraint". ValidationEventHandler has the following signature:

public delegate void ValidationEventHandler( object sender,
ValidationEventArgs args );

The ValidationEventArgs class provides the error code and error-message information for the event handler. Listing 16-9 shows a C# program that validates a user-specified XML file using schema validation.

Listing 16-9 Validation.cs: Code that validates an XML file using schema validation.

using System;
using System.IO;
using System.Xml;
public class Validation
{
  static bool gotError = false;
  public static void Main( String[] args )
  {
    /* make sure we were passed with a filename */
    if ( args.Length == 0 )
    {
      Console.WriteLine( "Usage: validation filename" );
      return;
    }
    try
    {
      /* Instantiates an XmlReaderWriter using the filename passed
         in from the command line */
      XmlTextReader reader = new XmlTextReader( args[ 0 ] );
      /* Specify the validation method */
      reader.Validation = Validation.Schema;
      /* Register event handler for reporting validation errors */
      reader.ValidationEventHandler += new ValidationEventHandler
( OnValidateError );
      /* traverse the whole document */
      while ( reader.Read() );
      if ( !gotError )
      {
        Console.WriteLine( "Document is valid" );
      }
    }
    catch ( Exception e )
    {
      Console.WriteLine( "Error: " + e.Message );
    }
  }
  public static void OnValidateError( Object sender, ValidationEventArgs args )
  {
    gotError = true;
    Console.WriteLine( "Error: " + args.Message );
  }
}

To compile Validation.cs, type in the following command line in a console window:

csc /r:System.dll /r:System.Xml.dll Validation.cs

XmlWriter

The XmlWriter abstract class is used to produce document streams conforming to the W3C's XML 1 and Namespaces Recommendations. XmlWriter handles many of the complexities in producing XML documents automatically, such as making sure that elements are properly closed and attribute values are quoted. XmlWriter also provides methods for writing typed data (for example, WriteInt32( ), WriteDouble( ), WriteString( ), and so forth).

WriteXML.cs in Listing 16-10 is a C# console program that generates an XML document to the console using XmlWriter.

Listing 16-10 WriteXML.cs: Code that generates an XML document to the console.

using System;
using System.IO;
using System.Xml;
public class WriteXML
{
  public static void Main( String[] args )
  {
    /* Instantiates an XmlTextWriter that writes to the console */
    XmlTextWriter writer = new XmlTextWriter( Console.Out );
    /* Use indenting for readability */
    writer.Formatting = Formatting.Indented;
    writer.Indentation = 4;
    /* Now write out our XML document */
    writer.WriteStartDocument();
      writer.WriteStartElement( "Persons" );
        writer.WriteStartElement( "Person" );
          writer.WriteStartElement( "Name" );
            writer.WriteStartAttribute( null, "firstName", null );
              writer.WriteString( "Albert" );
            writer.WriteEndAttribute();
            writer.WriteStartAttribute( null, "lastName", null );
              writer.WriteString( "Einstein" );
            writer.WriteEndAttribute();
          writer.WriteEndElement();
        writer.WriteEndElement();
        writer.WriteStartElement( "Person" );
          writer.WriteStartElement( "Name" );
            writer.WriteStartAttribute( null, "firstName", null );
              writer.WriteString( "Niels" );
            writer.WriteEndAttribute();
            writer.WriteStartAttribute( null, "lastName", null );
              writer.WriteString( "Bohr" );
            writer.WriteEndAttribute();
          writer.WriteEndElement();
        writer.WriteEndElement();
      writer.WriteEndElement();
    writer.WriteEndDocument();
    /* Don't forget to close the writer */
    writer.Close();
  }
}

Note that we have used the concrete class XmlTextWriter in WriteXML.cs. XmlTextWriter is a concrete implementation of XmlWriter for writing out character streams. It supports many different output types such as file, URI, and stream. It also provides pretty printing and other options properties for characteristics like indentation, namespace support, attribute quote character, and so on.


Languages Built on the .NET Framework

At the time of this writing there are about 20 programming languages—existing or under development from Microsoft and third parties—that can be used for building .NET applications. If that is not exciting enough, all these languages will be able to work together more seamlessly than ever before. This integration is possible because all these languages are compliant with the Common Language Specifications (CLS) and they all run under the same runtime. We will look at some of these languages here, namely C#, Visual Basic .NET, Jscript .NET, C++, and a few others.

The runtime is the core enabler of multilanguage program development and execution in .NET. The runtime manages the execution of code and provides services that make development easier. .NET compilers and tools expose the runtime's functionality and enable you to write code that benefits from this managed-execution environment.

Code that needs to execute under the runtime environment is referred to as "managed code." The runtime provides the following benefits to managed code:

  • Cross-language integration The runtime makes it easy to write components and applications that interact across languages. For example, you can define a class in C# that extends another class that was written in Visual Basic .NET.
  • Security The runtime provides an automatic built-in security mechanism for determining whether a particular managed component or application is allowed to run and that degree of trusted access that should be given to protected resources.
  • Versioning and deployment support The runtime uses metadata stored with managed code to ensure that your component or application has the specified versions of everything it needs.
  • Debugging and profiling services The runtime provides built-in services and tools to help developers debug and profile managed code easily.
  • Memory Management The runtime provides memory-management features that manage object lifetimes automatically.

C#

With .NET, Microsoft introduced a new programming language called C# (pronounced "c sharp"). C# is a modern object-oriented language and is designed from the ground up to work well within the .NET Framework. It provides seamless support for developing systems that rely on .NET, from managed components to XML Web services. Microsoft touts C# as a new language that combines the power of C++ with the ease of use of Visual Basic. Just as C++ and Visual Basic have been the de facto standards for developing applications for Windows, Microsoft hopes that C# will become the preferred language for developing .NET applications.

C# is consistently object-oriented. Everything in C# is an object, including primitive types like integer, floats, and the like. In addition, in line with a stricter object-oriented approach, C# ditches the concept of global functions, types, and variables. C# provides the equivalent static methods and variables to make code easier to read and less prone to naming conflicts. In addition, program execution no long starts from a global main function but instead starts with a call to a static Main( ) method in a class.

Many error-prone constructs and idioms in C++ are removed from C#. For example, C# does not support multiple inheritance and preprocessor macros, and instead uses interfaces and namespaces as a more elegant solution. C# also dispenses with the irksome -> pointer indicator and the :: namespace operator of C++. In C# everything is represented by a dot.

Data types have been significantly simplified in C#. For example, all character data is encoded as Unicode. A char is a char is a char. Gone are the confusing unsigned char, signed char, and wchar_t typed data types used in C++. A 64-bit integer is a long, not an __int64, and a 32-bit integer is simply int.

C# does retain some of the more powerful features of C++. For example, C# supports operator overloading, enumerations, and a variable number of method arguments. You can even use dangerous constructs like pointers, structs, and static arrays in C#, but they need to be used within explicitly declared unsafe classes and methods.

C# has great potential for becoming the tool of choice for .NET programming with its unique combination of power and simplicity. If you want to learn more about C#, check out the C# tutorials and references in the .NET Framework SDK documentation.

Visual Basic .NET

The price that you sometimes have to pay for running under the runtime is that specific languages might need to adapt to conform to it. Nowhere is this more evident than in Visual Basic .NET. Microsoft has introduced so many fundamental changes to its earlier Visual Basic that you should consider this a brand new language. As a result, upgrading previous versions of Visual Basic projects to Visual Basic .NET will typically require more preparation than a simple port.

Most of the new features in Visual Basic .NET stem from it having to comply with the CLS and accommodate the runtime and its associated programming model. Visual Basic .NET has many new object-oriented design features and much higher levels of type safety than previous versions of Visual Basic. The new object-oriented capabilities in Visual Basic .NET include support for full inheritance, parameterized constructors, method and properties overriding, method and operator overloading, and shared members.

Visual Basic .NET was also enhanced with modern programming constructs such as structured exception handling and threading. Some of the more significant features replaced or retired in the move from Visual Basic 6 to Visual Basic .NET are:

  • Default methods and properties are no longer supported.
  • Many keywords, including Let, Gosub, VarPtr, ObjPtr, and StrPtr, were retired.
  • Visual Basic .NET introduces new syntax for factors like class properties, declaring and initializing variables, and especially arrays.
  • Parentheses are now required for all methods, even those without arguments.
  • All arguments are now ByVal by default.
  • And, Or, and Xor are strictly for boolean operations. The new BitAnd, BitOr, and BitXor should be used for bitwise arithmetic.

Again, these are just some of the changes introduced in Visual Basic .NET. You should read the .NET Framework SDK documentation for a complete coverage of programming in Visual Basic .NET.

Jscript .NET

Together with C# and Visual Basic .NET, Microsoft introduced a new version of JScript called Jscript .NET. Jscript .NET combines the existing features of JScript with features usually found only in class-based and compiled languages. It is a true object-oriented language with full support for industrial-strength features such as inheritance and interfaces. And because JSript is a fully-fledged .NET-compliant language, you can create classes in Jscript .NET which can be used by other .NET languages and, conversely, extend classes defined in other languages.

Jscript .NET introduces many performance-improvement features. The most important of these, undoubtedly, is that Jscript .NET is a true compiled language, making its run-time performance comparable to C# and Visual Basic .NET. From a language perspective, Jscript .NET now provides strongly typed programming support through the explicit type declarations. Typeless and implicit typing programming is still supported through a new type-inferencing technology so existing scripts will still run.

Unlike Visual Basic .NET, Jscript .NET was specifically designed for full backward compatiblity, so all your existing code will continue to work just fine. All the new features, such as typing and class declarations, are optional. The new features, however, will allow you to reap the benefits of improved performance and robustness.

For more information about Jscript .NET, check out the JScript .NET section in the .NET Framework SDK documentation.

C++

C++ is alive and well in the .NET Framework. You can continue to use Visual C++ in .NET in the same old way, but to be able to take full advantage of the benefits of the runtime, Microsoft has extended C++ to provide support for "managed programming." These Managed Extensions for C++, as they are called, are mainly comprised of a set of new keywords and attributes. With Managed Extensions for C++, the runtime will provide automatic object lifetime and memory management (including garbage collection), enhanced exception handling, security, and finally debugging and profiling services for your C++ components and applications.

If you are a C++ programmer and you are interested in exploring managed programming, you should read the "Programming with Managed Extensions for C++" chapter in the .NET Framework SDK documentation.

Other Languages

As we mentioned earlier, besides C#, C++, and Visual Basic .NET, many other languages provided by third-party vendors can be used for .NET programming. For example, ActiveState Corp. (http://www.activestate.com) provides .NET implementations of the Perl and Python programming languages. Interactive Software Engineering is working with Microsoft to develop an implementation of the Eiffel programming language for .NET. Other vendors are also working on providing more programming languages, such as Pascal and even Java, for .NET. For the latest developments in this area you should visit the Microsoft MSDN Third Party .NET Resources Web site at http://msdn.microsoft.com/net/thirdparty/.


Conclusion

This book has taken you through many of the curves, obstacles, and straightaways of the XML highway. We have covered the language, how it should be used, and how to build successful enterprise-level applications. You should now have a good understanding of XML Web services and the .NET Framework. We've covered a lot of material.

Here is where the book ends and you go down your own path. Carefully consider the lessons learned within these pages, and be sure to make solid and sound decisions about how and why you are using XML. From the three of us, good luck!


Related Standards

XML, in many respects, is about standards, or the creation thereof. Even if you're just creating a schema for your own purposes, you're going down the path of creating a language to standardize your approach to something—to standardize your means of describing data and perhaps transmitting it to others.

XML has given individuals, companies large and small, and entire industries the ability to define the common grounds on which they describe and exchange data. And while XML was the driving force behind the ability to do this, like today's alphabet, it wasn't until people created languages and applications to use XML before it's abilities became exposed.

Today many of the standards created either build upon or utilize others. For instance, you might have a DTD document that is transported as a "payload" within the Microsoft BizTalk Framework. In this appendix we will discuss some of these related standards and how they pertain to XML. Because these efforts are often intertwined, this should be extremely useful in understanding what is out there, what XML is used for, and how you might tap it instead of writing your own solution.


Structured Standards

The structure of a document will often be one of the most important features for use and human readability. Take this book as an example—it has top-level headings, secondary headings, and so on. This is accomplished through the use of various "styles" that many of you have seen in word-processing programs. Many of these styles imply a structure, and that structure results in the table of contents: the ordering of the information in the book world.

When using XML, structure is important because it can show the parent-child relationship between elements. Take a hypothetical <name> element. If it's the child element of a <company> element, it most likely has a different meaning than if it's a child of a <person> element. This structure, although not necessary, makes the use of long element and attribute names unnecessary.

We've included unstructured.xml (Listing A-1) and structured.xml (Listing A-2) as examples. Although they contain the same information, the ability of a human or application to digest what is contained in the document is much easier in the more verbose structured.xml. Because XML requires only a single root element to be well-formed, the unstructured.xml document is actually a well-formed XML document.

Listing A-1 unstructured.xml: Using long names to imply structure.

<?xml version = "1.0" encoding = "UTF-8"?>
<entry>
  <employeecompanyname>Some Company, Inc</employeecompanyname>
  <employeename>Allen Wyke</employeename>
</entry>

Listing A-2 structured.xml: Applying structure with a hierarchy of elements.

<?xml version = "1.0" encoding = "UTF-8"?>
<entry type="employee">
  <company>
    <name>Some Company, Inc</name>
  </company>
  <name>Allen Wyke</name>
</entry>

The DTDs of XML are not the only method of applying structure to your documents and data. Within this section we will explore other structure-related specifications, such as Namepaces in XML, XML Schema, and the Resource Description Framework, and standards at your disposal.

Namespaces in XML

At its core, the purpose of XML is to be extensible. The ability of individuals, companies, and industries to define languages, markup, dialects, or all these factors to describe their data is powerful. However, this inter networked world has no use for a reinvented wheel. Many languages have similar elements, attributes, and structure. Contact information, for instance, usually contains a name, address, phone number, email, and the like.

Instead of having each schema define its own language, XML was created to be flexible enough to embed multiple languages into instance documents. This includes elements, as well as attributes. Flexibility allows an XHTML document, for instance, to also contain MathML markup. Let's use this as an example.

Listing A-3, sample-ns.xml, is an XHTML document that defines XHTML as the default namespace. In the <body> of the XHTML we have embedded some MathML, defining its own namespace for the enclosed elements.

Listing A-3 sample-ns.xml: Shows how namespaces in XML are used.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      lang="en" xml:lang="en">
<head>
  <title>XHTML and MathML Namespace Example</title>
</head>
<body>
  <p>The following is marked in MathML</p>
  <math xmlns="http://www.w3.org/1998/Math/MathML">
    <mrow>
      <mi>x</mi>
      <mo>=</mo>
      <mfrac>
        <mrow>
          <msqrt>
            <mrow>
              <msup>
                <mi>b</mi>
                <mn>2</mn>
              </msup>
              <mo>-</mo>
              <mrow>
                <mn>4</mn>
                <mo>&InvisibleTimes;</mo>
                <mi>a</mi>
                <mo>&InvisibleTimes;</mo>
                <mi>c</mi>
              </mrow>
            </mrow>
          </msqrt>
        </mrow>
        <mrow>
          <mn>2</mn>
          <mo>&InvisibleTimes;</mo>
          <mi>a</mi>
        </mrow>
      </mfrac>
    </mrow>
  </math>
</body>
</html>

We included MathML in with our XHTML. All we needed to do was define the namespace for the MathML elements, which is done with the xmlns attribute specifying the namespace URI so a parser will know how to interpret the content. Figure A-1 shows what this example looks like in the MathML supporting version of the open source Mozilla browser.

Figure A-1 Rendering our sample-ns.xml document in the Mozilla browser.
If you want to see this work for yourself, download the Mozilla version that supports MathML. Also download some extra fonts to support the MathML characters. You can obtain more information about this at http://www.mozilla.org/projects/mathml .

Associating a namespace with a block of elements (between the start and ending <math> elements in our example) is not the only way to declare namespaces. Declaring a namespace within the root element of the document is also possible, giving it a prefix and then prefacing all elements and attributes of that namespace with the same prefix. For instance, if we wanted to create a namespace in this manner for our previous example, we could have had the following:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:mml="http://www.w3.org/1998/Math/MathML"
        lang="en" xml:lang="en">

By declaring the MathML namespace here, we can use any of the elements and attributes of this language in the body of our document by placing mml: in front of them. Listing A-4, sample2-ns.xml, demonstrates this approach. Notice how we are able to include a MathML element inside an XHTML element without having the root <math> element present; the namespace allows us to do this.

Listing A-4 sample2-ns.xml: Using Namespaces in XML to include MathML within an XHTML document.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:mml="http://www.w3.org/1998/Math/MathML"
      lang="en" xml:lang="en">
<head>
  <title>XHTML and MathML Namespace Example</title>
</head>
<body>
  <p>The following is marked in MathML</p>
  <mml:mrow>
    <mml:mi>x</mml:mi>
    <mml:mo>=</mml:mo>
    <mml:mfrac>
      <mml:mrow>
        <mml:msqrt>
          <mml:mrow>
            <mml:msup>
              <mml:mi>b</mml:mi>
              <mml:mn>2</mml:mn>
            </mml:msup>
          </mml:mrow>
        </mml:msqrt>
      </mml:mrow>
      <mml:mrow>
        <mml:mn>2</mml:mn>
      </mml:mrow>
    </mml:mfrac>
  </mml:mrow>
  <p>Here is an example of including a MathML element right in the
  body of an XHTML element <mml:msqrt></mml:msqrt></p>
</body>
</html>

This is all we are going to cover on Namespaces in XML here. We used namespaces heavily throughout the book, so you should be familiar with them and the practice of using them.

If you would like more information on Namespaces in XML, please check out http://www.w3.org/TR/REC-xml-names .

XML Schema

XML Schema is, though controversial at times, considered to be the next generation of XML. It brings together the best of XML 1.0 with other related standards such as XML-Data, Document Content Description for XML (DCD), SOX, and Document Definition Markup Language (DDML), all of which are W3C Notes.

This effort, housed at the W3C, reached Recommendation status recently. The Recommendation is divided into three sections: Primer, Structures, and Datatypes. The Primer, Part 0, is a document to get you familiar with the core XML Schema language: Structures and Datatypes. It provides some good reading and examples, so if you are not familiar with XML Schema, it's worth the read.

Part 1, Structures, provides the mechanisms to define structure and any constraints that your data might need. This not only includes the DTDs defined in XML 1.0, but also those exploited through the use of namespaces. This part of the specification also relies on Part 2, Datatypes.

The final part of XML Schema addresses datatypes. It provides means by which datatypes can be defined in XML Schema or other XML-based languages. Thes datatypes defined in XML Schema represent a superset of the datatyping capabilities in XML 1.0's DTDs.

As an example, let's first build an XML DTD (entry.dtd in Listing A-5) for the structured.xml document we used earlier, and then show its XML Schema representation. Before we show you the schema, let's create a copy of the structured.xml document, called structured-valid.xml, and include the following line so it can be compared against our schema by a parser.

Listing A-5 entry.dtd: DTD that corresponds and describes the data in structured-valid.xml.

<!DOCTYPE entry SYSTEM "entry.dtd">
<?xml version='1.0' encoding='UTF-8' ?>
<!ELEMENT entry (company , name)>
<!ATTLIST entry  type CDATA  #IMPLIED >
<!ELEMENT company (name)>
<!ELEMENT name (#PCDATA)>

The XML Schema representation is shown in entry.xsd (Listing A-6). As you can see, XML Schema can be more verbose than a DTD, but at the same time, this example is a direct translation and does not add any of the benefits of XML Schema, such as the datatyping.

Listing A-6 entry.xsd: XML Schema version of our DTD.

<?xml version = "1.0" encoding = "UTF-8"?>
<xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema">
  <xsd:element name = "entry">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element ref = "company"/>
        <xsd:element ref = "name"/>
      </xsd:sequence>
      <xsd:attribute name = "type" type = "xsd:string"/>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name = "company">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element ref = "name"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name = "name" type = "xsd:string"/>
</xsd:schema>

XML Schema delivers many advancements and enhancements, so it would be wise to familiarize yourself with XML Schema and potentially build some applications using it. It is an incredibly powerful standard that can offer a wealth of abilities to implementers.

If you want more information on XML Schema, check out http://www.w3.org/XML/Schema. More good information is at http://www.ascc.net/~ricko/XMLSchemaInContext.html.

Resource Description Framework

One of the first efforts that emerged to enforce structure on data was the Resource Description Framework (RDF). RDF represents a manner in which metadata can be created to describe other data, as well as the basic syntax for the encoding and transmission of this metadata. The table of contents of this book can be considered metadata because it is not the actual contents of the book, but represents a description of what the book contains.

Behind the scenes, RDF is an XML language with the use of namespaces. It not only allows for the interoperability of metadata, but also for a machine-understandable description of Web resources (anything with a URI). These resources are described through a collection of properties, including property type and value, called RDF Description.

As an example, let's say we had a URI (http://mspress.microsoft.com/xml/example) that we wanted to describe as being created by a person at a particular company. Let's also say that the entry.dtd we created earlier contained the structure we wanted to use to define the name of this person and company. Listing A-7 shows our example, sample.rdf.

Listing A-7 sample.rdf: Sample RDF document describing a URI with our entry.dtd data model.

<?xml version="1.0"?>
<RDF xmlns    = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:EN = "http://mspress.microsoft.com/xml/entry.dtd">
  <Description
     about="http://mspress.microsoft.com/xml/example" >
    <EN:entry>
      <EN:company>
        <EN:name>Some Company, Inc.</EN:name>
      </EN:company>
      <EN:name>R. Allen Wyke</EN:name>
    </EN:entry>
  </Description>
</RDF>
The # symbol at the end of the RDF URI is important. It combines the namespace name with the local name to get the full URI of a property type.

The first task we perform after declaring it as an XML document is to define the default namespace (RDF) and a second namespace (Entry Name—entry.dtd). Using the RDF <Description> element, we specify that we want to describe the http://mspress.microsoft.com/xml/example URI. To define this we use the <name> element of (child of <company>) to hold the company name and the top level <name> element to define the individual.

This is a simplistic example, but it demonstrates the purpose of RDF: the ability to describe URIs (Web resources). In addition to the <Description> element, RDF also has other elements. Elements, for instance, that allow you to define Bags (<Bag>), Sequences (<Seq>), and Alternatives (<Alt>)—all types of collections. You also have several attributes at your disposal that do everything from identify individual elements within a collection to specifying the type of resource it is.

For more information on RDF check out http://www.w3.org/RDF .


Linking-Based Standards

With the birth of the Web came the need for and dependency on linking. Whether it was for being able to link to internal locations within a document, or to link to new documents altogether, the need for links was apparent. These are just a few of the reasons the <a> (anchor) element is so widely used and, frankly, a requirement for any Web-published document. But linking for HTML is not the only document type that needs this functionality. XML documents also need the ability to link to other resources. Additionally, efforts should be made to prevent broken links.

In this section we will introduce you to several efforts that revolve around linking, such as XLink and XPointer. As you will see, dramatic improvements are being made and the world of linking, as we currently know it, is changing.

The XML Linking Language

The XML Linking Language (XLink) builds upon the basic unidirectional <a> element links we are used to in HTML and XHTML. It not only defines and provides this familiar functionality, it also provides for more complex linking structures, including

  • Asserting linking relationships between two or more documents
  • Associating metadata with links
  • Expressing links that reside in a location separate from the linked resource

The XLink language itself consists of several attributes within the XLink namespace that define how the link is to be handled and how it works. The type attribute, which is one of the most important, defines the type of link. Table A-1 lists the possible types of links.

Table A-1 Types of XLink Links
Type | Description
arc | Provides traversal rules among the link's participating resources.
extended | Offers full XLink functionality.
locator | Address of the remote resource participating in the link.
none | Has no specific XLink meaning.
resource | Supplies local resources that participate in the link.
simple | Offers shorthand syntax for the common outbound link between two participating resources (like how the <a> element currently works).
title | Provides a human-readable title for a link.

The Mozilla browser was one of the first user agents to support XLink, and it provides the ability to perform some functions using the simple links as defined by the XLink specification. To demonstrate, xlink.xml in Listing A-8 contains three links using the XLink language. If you load this XML document into the browser, you will be able to click on the links to access the pages.

Listing A-8 xlink.xml: An XLink example which can be run in the Mozilla browser.

<?xml version="1.0"?>
<body xmlns:xlink="http://www.w3.org/1999/xlink">
  <para>Here is a list of my favorite links...
    <li xlink:type="simple"
        xlink:href="http://www.msn.com">Personal</li>
    <li xlink:type="simple"
        xlink:href="http://msdn.microsoft.com">Work</li>
    <li xlink:type="simple"
        xlink:href="http://www.microsoft.com">Industry</li>
  </para>
</body>

The XML Path Language

The XML Path Language (XPath), which is used to address parts of an XML document, represents shared syntax and semantics between XSLT and XPointer. XPath models a document as a tree of nodes, which includes element, attribute, and text nodes. It then provides the grammar for accessing the leaves of these nodes.

For instance, the syntax child::p selects the <p> element children of the context node. If you wanted all element children you would use child::*. You can even describe more complex locations such as the first <p> child of the context node with child::p[position( )=1]. Better yet, let's say you want to find the first one with the id attribute set to description. This would be accomplished with child::p[attribute::id=description][position( )=1].

This representation is referred to as the Location Path of the element instance. The child keyword, in these examples, is the axis of the XPath statement. Other axes are included in Table A-2.

Table A-2 Axes in XPath
Key | Description
ancestor | Contains the ancestors of the context node. This refers to the parent of the context node and the parent's parent and so on.
ancestor-or-self | Contains the context node and the ancestors of the context node.
attribute | Contains the attributes of the context node.
child | Contains the children of the context node.
descendant | Contains the descendants of the context node. This refers to a child or a child of a child and so on of the context node.
descendant-or-self | Contains the context node and the descendants of the context node.
following | Contains all nodes in the same document as the context node that are after the context node in document order, excluding any descendants or attribute and namespace nodes.
following-sibling | Contains all the following siblings of the context node.
namespace | Contains the namespace nodes of the context node.
parent | Contains the parent of the context node, if one exists.
preceding | Contains all nodes in the same document as the context node that are before the context node in document order, excluding any descendants or attribute and namespace nodes.
preceding-sibling | Contains all the preceding siblings of the context node.
self | Contains just the context node itself.

In addition to identifying portions of an XML document, XPath also has the ability to perform base string, numeric, and boolean manipulation. As the world of linking within XML documents (especially on the Web) begins to demand more out of user agents, XPath will play a major role in defining and describing documents for linking purposes.

You can find more information on XPath at http://www.w3.org/TR/xpath .

The XML Pointer Language

The XML Pointer Language (XPointer) is based on XPath and supports the addressing of internal parts of an XML document. This allows you to evaluate a document's structure and choose internal subsets based on criteria such as element types, attribute values, character content, and relative position. This is accomplished by defining extensions to XPath.

Using XHTML as an example, if you wanted to find the element with the id attribute value set to booktitle, you would use the following syntax:

xpointer(id("booktitle"))

This might, syntactically, look similar to XPath, as you would expect. But an extension to XPath in XPointer also allows you to specify a range. Maybe you want to locate everything from the title (id=booktitle) of the book to the end (id=bookend). You could do this with:

xpointer(id("booktitle")/range-to(id("bookend")))

XPointer, which is currently at a Candidate Recommendation state at the W3C, offers flexibility over the standard <a> element usage common in HTML and XHTML. However, because of the immaturity of this specification effort, we recommend you wait before implementing this in any of your projects. Once it has reached a Proposed Recommendation state, although not complete, you should be safe to use it as you see fit.

The XML Base

Linking, as we have seen, is an important piece of functionality. It allows us to build virtual documents and information repositories by linking from document to outside resources. But often, as we have commonly seen in HTML and XHTML, links are not absolute URIs, but are relative to the resource.

Rendering user agents, especially off line browsers, might need to know the absolute URIs for their processing. To handle this type of situation Web developers use the XHTML <base> element, which can specify the base URI for any relative URIs a given document might contain. The need for this is no different in XML. In response and in addition to the other efforts the XLink Working Group is working on, XML Base was born.

XML Base is a short specification whose sole purpose is to enable XML developers using links the ability to specify base URIs. This is accomplished by including an xml:base attribute in the root element with a value equal to the base URI. Additionally, you can specify this attribute in child elements if you: (a) do not want to apply the base URI to all elements in a document, or (b) want to override the base URI for a specific element and any child elements it has.

As an example, let's take a look at sample-base.xml in Listing A-9. Here we define a base URI of http://www.microsoft.com/employees/ for the entire document, but we override it for the list of sites we have included.

Listing A-9 sample-base.xml: A simple demonstration of defining a base URI for a whole document, and then overriding it for specific elements and their children.

<?xml version="1.0"?>
<user xml:base="http://www.microsoft.com/employees/"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <name>R. Allen Wyke</name>
  <phone>919.555.1212</phone>
  <!-- base URI applies to the following 3 child elements -->
  <sites xml:base="/www/">
    <li xlink:type="simple"
        xlink:href="pesonal.xml">Personal</li>
    <li xlink:type="simple"
        xlink:href="work.xml">Work</li>
    <li xlink:type="simple"
        xlink:href="industry.xml">Industry</li>
  </sites>
  <!-- base URI defined in root element applies here -->
  <img src="photo.gif" alt="My Photo" width="40" height="40" />
</user>

This is all the XML Base specification is: the xml:base attribute. If you want to learn more about this standard and how user agents are supposed to conform, please see http://www.w3.org/TR/xmlbase .


Transformation and Remote Object Access

Creating structured documents and providing a means to link to and from them gives developers a wide range of abilities. However, structures are not always in a format you can process, and the method in which you transmit this data could be inconsistent with other enterprises. These are common issues that arise when implementing XML-based solutions and they need to be addressed for compatibility and flexibility reasons.

In the following two subsections we will cover a few standards, such as XSLT and SOAP, that help ease the pain these situations present. These are both complete, in W3C standards terms, and you will find applications already leveraging their power.

Extensible Stylesheet Language Transformations

XSLT, which we have covered in part in earlier chapters, is a language that allows you to transform one document schema into another. This is tremendously powerful when you use XML in the enterprise because it allows you to accept various documents structures, by transforming them into a structure that you natively support. The process is simple. Accept another document structure, apply the appropriate XSLT style sheet, and transform the document into a schema you can utilize in your processing.

XSLT is a useful approach for transforming your content markup so you can publish to multiple devices. If you define your content within an XML language, applying an XSLT style sheet to transform the document into XHTML, WML, or any other device-specific markup or other text format is easy. This allows you to create content once and, through the application of transformations, publish it anywhere.

The best way to learn and understand XSLT is through an example. Suppose we want to take the structured-valid.xml document we created earlier and transform it into valid XHTML. Let's rename it structured-trans.xml so as not to get confused. To do this we will not only need to include the header and body elements of the document (<html>, <head>, <body>, and so forth.), but we must also transform our <entry>, <company>, and <name> elements into valid XHTML elements. As a refresher, here is Listing A-10, our structured-trans.xml document:

Listing A-10 structured-trans.xml: An instance document of our entry.dtd schema.

<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE entry SYSTEM "entry.dtd">
<entry type = "employee">
    <company>
        <name>Some Company, Inc</name>
    </company>
    <name>Allen Wyke</name>
</entry>

Once the input document has been created we must create an XSLT file that contains the appropriate mapping from our data model defined in entry.dtd to XHTML. For this example we will include the company name in the <title> as well as in the <body> of our document. The body of the document will include a <p> element with our transformed text. Listing A-11 is our XSLT document.

Listing A-11 transform.xsl: The transformation document.

<?xml version="1.0"?>
<xsl:stylesheet
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
  <title><xsl:value-of select="//entry/company/name"/></title>
</head>
<body>
  <h1>User Information</h1>
  <p>
    <strong>
      Company Name:
    </strong>
    <xsl:value-of select="//entry/company/name"/>
    <br />
    <strong>
      Employee Name:
    </strong>
    <xsl:value-of select="//entry/name"/>
  </p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

If you take a close look at this style sheet you will notice it`s fairly simple. The <xsl:value-of> element is used to specify the element, including structure, that you are referencing. For instance, in the <title> element the style sheet basically says that the <name> child element of the <company> element, which itself is a child element of <entry>, should be contained between the beginning and ending <title> elements.

Once the style sheet has been defined, all you have to do, assuming the XSLT style sheet is in the same directory, is include the following line in your structured-trans.xml document just under the <?xml version = "1.0" encoding = "UTF-8"?> declaration. This tells the parser that when the structured-trans.xml document is loaded it should apply the transform.xsl style sheet while processing.

<?xml-stylesheet type="text/xsl" href="transform.xsl"?>

If we load the structured-trans.xml example in Microsoft Internet Explorer (with MSXML version 3 or later) you should see something like Figure A-2.

Figure A-2 Results of applying our transformation to structured-trans.xml and creating an XHTML document.

The topic of XSLT, as you might imagine, is much broader than this simple example. We used XSLT in several of our chapter projects, so refer to them for details.

If you want more information on the XSLT Recommendation, check out http://www.w3.org/TR/xslt .

The Simple Object Access Protocol

SOAP is a protocol for exchanging data in truly distributed and decentralized networks. It not only contains information on how to process its payload, but also a set of encoding rules for expression instances of application-defined datatypes and a procedure for representing remote procedure calls and the return of the results. We like to think of SOAP as the United Parcel Service of digital data. It not only contains the data, but also the instructions for what to do with it and how to return the results of any processing that occurred.

Because SOAP is a major component of Microsoft's .NET initiative, we dedicated all of Chapter 14 to covering it and how it works. If you are hungry for more information on SOAP, you can read up on the most recent version standard at http://www.w3.org/TR/SOAP .


Other Standards

You should be aware of several other standards before building your applications. Again, if you have a need or a problem to solve, someone else or another company probably had the same need as well. Knowing and understanding these standards will accelerate your time to deployment.

In this section we will look at a few standardization efforts. Specifically, we are going to look at one effort that attempts to define a method for including pieces of XML documents within other documents, and a second effort that defines a means with which to query XML documents for information.

XML Inclusions

XML Inclusions (XInclude) defines a method by which XML documents can be, at processing time, included in other documents. In much the same manner that Server Side Includes (SSI) work, XInclude syntax defines the grammar of how to reference other documents within a parent document.

For example, XMLInclude could be used if you wanted to include today's news on a page within your site, but the news was contained in a separate document. Historically you would've accomplished this with server-side processing an <iframe> element, but this approach does not really make the data "part" of the parent document. Using XInclude, however, you can reference your news document within the body of the document in which you want to include this information, and when processed, a single document will result.

To help you better understand this, we've built xinclude.xml (Listing A-12), a simple XHTML document, that contains a reference to a second document for inclusion.

Listing A-12 xinclude.xml: An XHTML document that contains a separate XIncluded document.

<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:xinclude="http://www.w3.org/1999/XML/xinclude"
      lang="en" xml:lang="en">
<head>
  <title>XInclude</title>
</head>
<body>
  <h1>Today's News</h1>
  <xinclude:include href="news.xml" />
</body>
</html>

As you can see, the syntax for this inclusion is simple. Using the XInclude namespace, we use the <include> element to specify the news.xml document for inclusion. Listing A-13, the news.xml document, only contains some basic information for our news stories.

Listing A-13 news.xml: A sample news document we will use for inclusion.

<h2>Sports</h2>
<p>
  The NHL is nearing the end of their season, and the race
  for the cup is in full swing. <a href="nhl.xml">Read
  more...</a>
</p>
<h2>Travel</h2>
<p>
  Airfares continue to go down as the economy slows.
  <a href="nhl.xml">Read more...</a>
</p>

When processed, the resulting document (xinclude-result.xml), after inclusion, will look like the following:

<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:xinclude="http://www.w3.org/1999/XML/xinclude"
      lang="en" xml:lang="en">
<head>
  <title>XInclude</title>
</head>
<body>
  <h1>Today's News</h1>
  <h2>Sports</h2>
  <p>
    The NHL is nearing the end of their season, and the race
    for the cup is in full swing. <a href="nhl.xml">Read
    more...</a>
  </p>
  <h2>Travel</h2>
  <p>
    Airfares continue to go down as the economy slows.
    <a href="nhl.xml">Read more...</a>
  </p>
</body>
</html>

XInclude might look like a relatively simple standard, but if you think of the overall picture of creating a semantic Web where content is not replicated, but referenced and included, it presents a compelling message for managing your content.

XInclude is currently a Working Draft at the W3C, so there could be substantial changes in the specification before it become a complete Recommendation. For this reason, we limit our coverage here to the basics.

XML Query

One of the most powerful aspects of databases is the ability to query them for information—not only bulk information, but also for information that only fits specific criteria. The efforts of the XML Query Working Group at the W3C revolve around an attempt to create a standardized method to query XML documents. The desired result was a method to access information from real and virtual (remember XInclude) XML documents like you would a database.

The XML Query work revolves around three core items. First is the XQuery 1.0 and XPath 2.0 Data Model, which is a node-labeled, tree-constructor representation of an XML document that also includes the concept of node identity. Second is XQuery 1.0 Formal Semantics, which defines the operators and grammar for querying XML documents. Finally, the group created XQuery, which is a query language built on XQuery 1.0 Formal Semantics, for querying XML documents.

The works of the XML Query Working Group are currently at a Working Draft state, and therefore unstable at this point. While we recommended you read and familiarize yourself with the standard, we do not advise you to begin building applications using the guidelines it defines. However, Microsoft has created a prototype application using a version of XML Query that you can experiment with at http://131.107.228.20/. If you need to build applications soon, we recommend you contact the working group at http://www.w3.org/XML/Query and ask to be involved. Otherwise, wait until the Proposed Recommendation is published.


Final Thought

Following and understanding related XML efforts is a big part of being active in and with XML. Chances are, if you have a need, someone else had the same need and possibly solved the problem. By taking extra time and doing research, in addition to the time you spend staying on top of the latest languages and dialects, you can potentially reduce the amount of work you have to do.


XML Software

When XML was first developed and declared an official Recommendation by the W3C, everyone monitoring its progress, including the overall technical industry, realized that XML was going to be a major part of their future systems. Adoption of XML was slow, however, and people found its semantics hard to understand and digest.

This problem was not because XML is terribly complex, but more a case of "old habits die hard." Everyone was used to using HTML as their means of marking up content and sending it to others. XML, for true implementations, demanded more than just a structured document. Instances needed, though did not require, to be validated and servers or other receiving software had to be written to process the documents when transmitted. For this reason it was nearly two years before any successful applications emerged that truly decreased the amount of time it took to create XML solutions.

However, the time of emerging XML-based software tools, servers, and other applications is now here. Microsoft helped lead this charge and many other vendors created tools, components, and entire solutions based around the flexibility and syntax of XML. As you found out with this book, Microsoft's .NET initiative is not only a type of solution, but also a reflection of an entire shift to communicating and enabling via XML.

In this appendix we will introduce you to some of the tools you can tap for XML development. Because XML is not a platform-dependent language, we have tried to provide information on tools, such as those from Altova and TIBCO Extensibility, for all major platforms so you do not feel locked into the Microsoft Windows environment. Conversely as you saw throughout this book and especially in Chapter 16, Microsoft helped enable this cross-platform- and environment-independence by basing its .NET Framework on open standards.


Development Tools

For many of us development tools are more than just a way of life—they represent an absolute necessity. Even for those of us who often prefer a good text editor and the command line, high-end development tools usually help accelerate our application creation and ease our learning curve, especially for tag-based technologies like XML. These IDEs get us started quickly and allow us to reach our goals faster.

Over the next few pages we are going to and cover some of the more popular development tools on the market today. Because Windows and UNIX-based systems, such as Linux, often represent the majority of application-development environments, we will focus on applications that run on these operating systems. For any Mac OS users out there, especially OS X users, we will even have a few for you.

Schema and DTD Tools

The first type of tools we are going to look at are XML DTD and Schema (as in XML Schema) creation tools. Back in 1998 and even in early 1999, these types of tools basically did not exist. Most schemas were created using a text editor, like Notepad, and validated using command-line applications. Today, however, IDEs exist that provide advanced features, such as real-time validation and error reporting, which lay the foundation for our XML solutions.

In this section of the appendix we are going to share some details about XML Authority and XML Spy—two leading schema development editors.

XML Authority

XML Authority ( http://www.tibco.com ), which was developed by a company called Extensibility (later purchased by TIBCO), was one of the first schema-development environments created for XML. Its simple and easy-to-use interface, shown in Figure B-1, allowed developers to quickly and easily create schemas and verify them in realtime. While it supplied a means by which you could create, modify, and delete elements and attributes, it also provided a visual representation of your model, the ability to insert comments easily, and direct source modification for those of us who need to touch the code directly at times.

Figure B-1 TIBCO Extensibility's XML Authority product.

One of the best features of the XML Authority product, besides its UI, is its support for multiple dialects. For instance, you can open an XML DTD and, with the common Save As... menu option, save the equivalent XML Schema, SOX, SGML, Microsoft BizTalk-compatible, and other representations. It performs the transformation from one language to another.

Table B-1 displays the supported dialects of the software as of version 6.1. In this table we have included the name of the dialect, a description, and a quick example of how their outputs vary for a simple element definition. The example shown later will be based on the following XML DTD definition.

Table B-1 Dialects Supported in XML Authority
Dialect | Description | Example
BizTalk Compatible | An XML-based design framework for implementing schemas and a set of XML tags used in messages sent between applications ( http://www.biztalk.org ). | <ElementType name = "example" content = "textOnly" model = "closed"/>
DCD | A structural schema facility for specifying rules covering the structure and content of XML documents ( http://www.w3.org/TR/NOTE-dcd ). | <ElementDef Type = "example" Content = "Closed" Model = "Data"/>
DDML | The Document Definition Markup Language encodes the logical, as opposed to the physical, content of DTDs ( http://www.w3.org/TR/NOTE-ddml ). | <ElementDecl Name = "examle"><Model> <PCData/></Model> </ElementDecl>
SGML | The Standard Generalized Markup Language, which laid the foundation for both HTML and XML ( http://www.iso.ch - search for ISO 8879:1986). | <!ELEMENT example (#PCDATA)>
RELAX | The Regular Language description for XML is used for describing XML-based languages ( http://www.xml.gr.jp/relax ). | <elementRule role = "example" type = "string"/><tag name = "example"/>
Schema Adjunct | An XML-based language developed by TIBCO Extensibility and used to associate domain-specific data with schemas and their instances ( http://www.extensibility.com/resources/saf.htm ). | Not Applicable
SOX version 2 | The Schema for Object-Oriented XML for defining the syntactic structure and partial semantics of XML document types ( http://www.w3.org/TR/NOTE-SOX ). | <elementtype name = "examle"><model> <string/></model> </elementtype>
XDR | XML Data Reduced, which is a dialect based on a subset of XML-Data (http://msdn.microsoft.com/library/default.asp?URL=/library/psdk/xmlsdk/xmlp7k6d.htm). | <ElementType name = "example" content = "textOnly" model = "closed"/>
XML DTDs | The XML 1.0 language ( http://www.w3.org/TR/2000/REC-xml-20001006 ). | <!ELEMENT example (#PCDATA)>
XML Schema | XSD is the recently released standard from the W3C that is essentially the next generation of schema description ( http://www.w3.org/XML/Schema ). | <element name = "example" type = "string"/>
<!ELEMENT example (#PCDATA)>
You will notice that some of these examples look the same, which should provide some insight into how some of these dialects are closely related and even based on one another.

Exporting different dialects is not the only feature of the XML Authority application. It also has the ability to import a variety of different data sources and build an XML representation out of them. This includes objects like COM, Java, and ODBC. Table B-2 contains a list of the types of data it can import along with a description.

For most of these items you can choose to import the data as elements or attributes in the resulting schema. For instance, if you're importing data from a database through ODBC, you can select to have the database column names as either child elements of the table name or attributes.
Table B-2 Import Data Sources for XML Authority
Type | Description
COBOL Copybook | Allows you to import COBOL copybooks, where the COBOL data declarations will be represented as XML elements.
COM | Allows you to import COM objects.
Internal Subset | If information on the schema has been included as an internal subset of an instance document, you import this information.
Java class | Allows you to import a Java class and store the properties as elements or attributes.
LDAP | Allows you to import an LDAP (Lightweight Directory Access Protocol) directory structure.
ODBC | Allows you to connect to a database via ODBC and import the tables available on that source. It does not create the overall database hierarchy with this information—only that of a particular table.
SGML | Allows you to import an SGML DTD for conversion to an XML schema.
Text | Allows you to import the column headers of a tab-delimited text file. Once you have defined your model after these headers, you can create instance documents with the column values.
XML Document | A well-formed XML document.

These are just a few of the features of XML Authority, which provides a lot of flexibility for those of you transitioning from one type of data to another. In addition to these features, this application also allows you to export the visual representation of your schemas in GIF format, provides auto-complete features while typing, has a simple interface for element and attribute lists, and even communicates directly with its XML repository server, XML Canon/Developer, which we will discuss later.

If you want more information on XML Authority visit http://www.tibco.com . The application is currently sold as part of a suite (XML Turbo) with two other applications. Because of the direction TIBCO appears to be taking with their software products, we are unsure how long this deal will last. So if you are interested in this application, we recommend you purchase it soon. It might only be available as part of a larger suite in the near future.

XML Authority is available on Windows, Unix, and Mac OS platforms. Note that earlier versions are available only on Mac OS 9 and earlier because of limitations in its support for Java. MacOS X users, however, will be able to use the most recent versions.

XML Spy

Another popular XML development tool is XML Spy ( http://www.xmlspy.com ), shown in Figure B-2. This tool allows you to create and validate even more types of XML documents and schemas than XML Authority, as seen in Table B-3, as well as manage entire projects that can be checked in to and out of Microsoft's Visual SourceSafe. The one drawback is that it only runs on Windows systems, so dedicated Mac OS and Unix-based users will need to look for another solution.

Figure B-2 The XML Spy editor.
XML Spy allows you to edit HTML and ASP files, but they are not XML based. Additionally, you can add other types of documents to create or edit within the preferences.
Table B-3 Spy-Supported Formats
File Extension | Description
.biz | BizTalk
.cml | Chemical Markup Language
.dcd | Document Content Description
.dtd | Document Type Definition
.ent | Entity Sets
.math and .mml | Mathematical Markup Language
.mtx | MetaStream XML
.rdf | Resource Description Framework
.smil | Synchronized Multimedia Integration Language (SMIL)
.svg | Scalable Vector Graphics
.wml | Wireless Markup Language
.xdr | XML-Data Reduced
.xhtml | Extensible Hypertext Markup Language
.xml | XML Document
.xsd | XML Schema
.xsl | XSL Stylesheet
.xslt | XSL Transform

If you take the time to step through the tutorial provided with the applications or available on the Web site, you will find XML Spy to be an easy-to-use, yet incredibly flexible and complete tool. Features like autocomplete and autosuggest, the ability to visually edit your schemas or edit the XML source directly, and being able to create HTML- or Microsoft Word-formatted documentation (see Figure B-3), makes it one of the most complete and inexpensive tools available today.

Figure B-3 An XML Spy documentation generation feature.

Document Editors

Once a schema has been developed, the next step is to create instance documents of the schema. This helps us verify that our schemas reflect the data models we want them to represent. The truest test is to apply real data to a model that is supposed to organize and describe that data. If you find extra data that is not described, or descriptions for data that doesn't exist, or even improper definitions, these instance documents will expose the inefficiencies and allow you to correct them.

XML Notepad and BizTalk Editor

Microsoft XML Notepad (http://msdn.microsoft.com/xml/notepad) was one of the first applications developed to create and edit XML instance documents. Although it has not been updated for some time, it allows you to edit and create well-formed XML documents. Its simple interface makes it easy to use for beginners or programmers trying to create XML documents quickly. Figure B-4 displays the XML Notepad.

Figure B-4 XML Notepad.

Now that the Microsoft BizTalk Server, which we will discuss later, has been released, you should use the Microsoft BizTalk Editor instead of XML Notepad. As you can see from Figure B-5, the applications have some similarities, and XML Notepad obviously laid the foundation for the BizTalk Editor application. We discussed the BizTalk Server and the other applications within this solution suite in Chapter 15, so check there if you have questions. For the moment, if you do not have the BizTalk Server, XML Notepad is still a good tool for starting out.

Figure B-5 The BizTalk Editor.

XML Instance

XML Instance, seen in Figure B-6, is TIBCO Extensibility's XML document instance editor. Some of the benefits of this application include its ability to arrange the attribute fields so they are on one line (look at Manager, Degree, and Programmer for an example) and that it provides real-time validation of the documents against the governing schema, which you can set if the document does not already specify one. XML Instance also allows you to save documents to a URL or directly to XML Canon, TIBCO Extensibility's XML repository server.

Figure B-6 The XML Instance XML document editor.

While this tool provides some easy-to-use features, it has some limitations as of the writing of this book. For instance, it allows you to quickly insert attributes, elements, comments, and so forth by clicking the appropriate icon on the Insert bar between the document and error pane, but this bar can, as of version 2.1, only support the display of eight items.

We also found that if you need to change something in your schema file, you have to reassign that schema to the document in XML Instance to update the validation. Doing so will also lose any expanded or collapsed items. This is not a problem for small documents, but can be annoying for longer ones.

XML Instance runs on Windows, Unix, and Mac OS systems. Like XML Authority, current versions for the Mac OS run on OS X only.

Komodo

ActiveState Komodo, which is nearing the release of version 1.2, is an IDE created by ActiveState ( http://www.activestate.com). ActiveState is the company that worked with Microsoft to provide a Win32 port of the popular programming language Perl. The application, shown in Figure B-7 debugging an XSLT document, allows you to textually edit XML and XSLT documents and data models.

Figure B-7 A Komodo IDE debugging an XSLT document.

It has some useful features but does not provide the robust environment that the other editors have. While the visual representations and document-generation features look like "nice to haves" on the other applications, not having them in Komodo exposes the need for them. Komodo runs on Windows and Linux systems, which is a plus, and because it is built on top of an open source application, ActiveState's ability to offer it on other systems is natural.

If you are programming in Perl, Python, PHP, or XSLT along with XML, Komodo provides some useful debugging features for these languages.


Servers and Document Management

Schemas creation tools and document editors are not the only types of tools needed for the XML developers. Once you have your data and have it described, you need to utilize and manage it. You do not want changes to be applied to schemas or documents without tracking them and being able to roll back. Additionally, you want to be able to organize your schemas and corresponding documents for quick retrieval and usage. You want to be able to accurately document your schemas, and you want to publish them so they can be referenced in instance documents for validation against parsers.

Within this section of the appendix we will look at servers from Microsoft and TIBCO Extensibility.

Microsoft

When Microsoft announced its BizTalk Framework and Web site, everyone knew that it was only a matter of time before the company built applications to support this initiative. Roughly two years later the BizTalk and Microsoft SQL Server 2000 servers are out, and both are part of Microsoft's .NET line of servers. Optimized for XML data interchange, these applications offer a range of features and functionality needed while working within an XML environment.

BizTalk Server

BizTalk Server is a new server product that provides an entire suite of tools and means by which you can coordinate business problems with many different sources of data. It not only contains the XML editor we previously mentioned, but also includes other tools used for mapping one data structure to another (Figure B-8), tracking documents, and processing.

Figure B-8 The BizTalk Mapper.

Built to conform to XML-based standards, like SOAP, and to Microsoft's .NET Framework, the BizTalk Server is one of the core servers for a successful XML solution deployment. As we mentioned earlier, we covered the BizTalk Server in detail in Chapter 15.

The SQL Server

With the latest installment of SQL Server (2000 at the time of this writing), the Microsoft team has provided a rich set of tools and functionalities for all XML developers. It not only provides the ability to query a database through an XML statement, but also provides a means by which you can insert and update data within the SQL Server. This is a key component when you build .NET-enabled Web Services.

Accessing the data is only part of the XML feature set of the SQL Server. It also adds an XML view of this relational data, as well as being able to map XML data into the appropriate tables. The server also has the ability to show how XML data looks when received and how it should look after a transaction. SQL Server, through its XML Updategrams functionality, will create and run the necessary SQL queries to perform the task.

TIBCO Extensibility

As we saw earlier in the appendix, TIBCO Extensibility has created several popular tools for working with and managing XML schemas. However, schema development is not the only type of tool they provide. They also have applications ranging from acting as a repository and versioning to autodocumenting and batch dialect conversion (that is, DTD to XSD).

XML Console

XML Console, shown in Figure B-9, rounds out the offerings from TIBCO Extensibility on the XML document and schema creation and editing side. It provides features such as project management, batch conversion, and, as you can see in Figure B-6, batch validation. Additionally, XML Console allows you to automatically generate documentation on the schemas in much the same manner as XML Spy.

Figure B-9 XML Console rounds out the TIBCO Extensibility XML document editing suite.
The combination of XML Authority, XML Instance, and XML Console can be purchased in a package called XML Turbo. This suite has roughly the same feature set available in XML Spy.

Like XML Authority and XML Instance, XML Console allows you to save to a URL or to the XML Canon repository server we will introduce you to next.

The XML console runs on Windows, Unix, and Macintosh OS systems. Like XML Authority, current versions for the Macintosh OS run on OS X only.

XML Developer/Canon

TIBCO Extensibility was one of the first companies to build and release a repository server geared directly at XML development. While many of you might say that the SourceSafe or CVS systems have everything you need, XML Developer/Canon, or XML Canon (in reference to canonical) for short, not only stores and provides version control for your files, but also offers XML-specific features. These features range from those that you expect, like checkin and checkout, to features such as finding reusable portions of a schema and autodocumentation.

XML Developer/Canon is one of, if not the, most comprehensive XML-repository servers we have seen. It provides all the versioning, differencing, and source management one would expect, as well as complete Web access, searching, and browsing that can be controlled with various levels of security. This not only allows your internal development teams to store and track internal XML schemas and documents, but also allows you to provide access to partners and affiliates in a secure manner. It even allows for stage tracking (that is, life cycles such as design, testing, production, and the like), reporting, and automatic e-mail notification for those who have chosen to be notified when changes occur to a specific file.


Software Development Kits (SDKs)

The last, and perhaps most important, topic we include here is a list of SDKs you might find useful when building and deploying XML solutions. In Table B-4 we included the name of each SDK, a corresponding URL, and a brief description.

Table B-4 Available SDKs and Other Information
SDK | URL | Description
BizTalk Framework 2 | http://www.microsoft.com/biztalk/techinfo/framwork20.asp | Defines the BizTalk documents and message.
MSXML 4 | http://msdn.microsoft.com/xml/general/newinaprilre.asp | The newest Microsoft XML Parser, supports XSD.
.NET Framework | http://msdn.microsoft.com/downloads/default.asp?URL=/code/sample.asp?url=/msdn-files/027/001/580/msdncompositedoc.xml | Currently in beta, contains everything you will need to write and test .NET applications.
SOAP Toolkit 2 | http://msdn.microsoft.com/downloads/default.asp?URL=/code/sample.asp?url=/msdn-files/027/001/580/msdncompositedoc.xml | Support for SOAP messaging, along with documentation and examples.
UDDI | http://microsoft.com/downloads/release.asp?ReleaseID=24822 | Provides all the tools needed for a Visual Basic programmer to interact with the UDDI registry. See http://uddi.microsoft.com/ for more information.
Xalan | http://xml.apache.org | An XSLT processor for transforming XML documents into HTML, text, or other XML document types. Supports the XSLT, XPath, and TRAX standards and is available in C++ and Java versions.
Xerces | http://xml.apache.org | An XML parser that supports XML 1.0, DOM Level 1 and 2, SAX 1 and 2, and XML Schema. Available in C++, Java, and Perl implementations.


Conclusion

XML can be easy to learn and follow. You might even find it easy to build applications to utilize XML data, but XML development tools are definitely necessary. They not only provide a centralized environment where you can perform your XML work, but they also accelerate the learning curve for new technologies and XML-based languages.

In this appendix we showed you several of these tools and provided various options so you can make an intelligent choice for your own development environment. As we are sure many of you seasoned developers know, standardizing development environments for your applications and solutions is an often overlooked but important step to success.


About the Authors

R. Allen Wyke

R. Allen Wyke, of Durham, North Carolina is Vice President of Technology at the Environment, Health, and Safety (EHS) solutions company, Blue292. At Blue292 he leads the engineering team and works with the executive team ensure their products services have the proper vision and direction. He is constantly working with XML, HTML, JavaScript, and various other Internet technologies in designing and developing the online EHS software and services that Blue292 has to offer.

Allen, who wrote his first computer program at the age of eight, has programmed in everything from C++, Java, and Perl to Visual Basic and JavaScript. He also has experience in other "languages" such as SQL, HTML/XHTML, DHTML, and XML. Allen has written roughly a dozen books on various Internet technologies and previously wrote the monthly Webmaster column for SunWorld, and a weekly article, Integrating Windows and Unix, for ITworld.com.

Brad Leupen

Brad Leupen is the Chief Technology Officer for the education software company Entrinsik Inc. At Entrinsik Brad brought Web-based products to market that utilize cutting-edge technology such as XML and XSLT. Entrinsik's latest product is entirely XML-based and relies on the technology for its user-interface, middleware components, and data storage. In addition to being a technology strategist, he has also worked as a product manager and implementation specialist for Entrinsik.

Brad has been programming applications since childhood. He worked as a professional programmer over summers in high school before attending Harvard University, where he earned an A.B. with honors in Computer Science. Brad helped to teach several undergraduate courses in programming and operating systems in his time at Harvard. Upon graduation he accepted a job with Entrinsik to become their R&D specialist. Brad has extensive practical knowledge of XML/XSLT, HTML/XHTML, DHTML, Java, J2EE, C++, Visual Basic, and several database technologies.

Sultan Rehman

Sultan Rehman, based in Hong Kong, is the Chief Software Architect for Engage Asia/Australia, where he is responsible for designing and implementing next-generation ad serving and optimization solutions. He specializes in using object-oriented designs for implementing mission-critical distributed systems. One of his current projects involves developing a high-performance and extensible XML Server that exposes a legacy database schema through a set of DTD-constrained XML documents, XLST templates, and XML-RPC methods. Sultan is also working on a configurations management system that uses XML at all levels; from the back store to the configuration documents. He is considering open-sourcing this project and is talking to OpenNMS.

Programming became Sultan's passion more than a decade ago when the boss at his summer job's bought an IBM XT. He quickly mastered BASIC and moved on to Turbo Pascal, C++, Delphi, and eventually to his current favorite, Java. He first got hooked on XML in 1998 after upgrading to Microsoft Internet Explorer 5 and saw the possibility of creating a cross-platform way of managing and communicating data and information. When he's not bathing in the glow of a computer screen, Sultan can be found playing "chase the laser pointer" with his eight cats.


About This Electronic Book

Every effort has been made to ensure that the text of this eBook matches the text of the paper version as closely as possible. For usability reasons, formatting changes may have been made to the eBook version.

Tell Us What You Think

We need to hear from you regarding your experience with our eBooks. Tell us what you like, don't like; which features you use, and which features you would like to see in future versions of our eBooks. Send your comments to epublish@microsoft.com. Please note that technical support is not offered through this alias.


Create a List of Favorite Topics

  1. Locate the topic you want to make a favorite topic.
  2. Click the Favorites tab, and then click Add.

To return to a favorite topic, click the Favorites tab, select the topic, and then click Display.

If you want to rename a topic, select the topic, and then type a new name in the Current topic box.

To remove a favorite topic, select the topic and then click Remove.


External Links

This eBook may contain links to Web sites. All hyperlinks within the text were valid at the time this eBook was published. Due to the nature of the World Wide Web, we cannot guarantee that all links to Web sites are still valid after the release date of the eBook.


Enlarge Graphics

Many of the graphics shown in this book are quite large. To improve the readability of the electronic version, reduced versions of these graphics are shown in the text. To see a full-size version, click on the reduced graphic.


Use Accessibility Shortcut Keys

The following keyboard shortcuts can be used for navigation.

For more information about accessibility features and services, visit the Microsoft Accessibility Web site.

For the Library:

To | Press
Close the eBook. | ALT+F4
Switch between the eBook and other open windows. | ALT+TAB
Print a topic. | Right-click in the Topic pane.
Move back to the previous topic. | ALT+LEFT ARROW
Move forward to the next topic (provided you have viewed it just previously). | ALT+RIGHT ARROW
Refresh the topic that appears in the Topic pane (this is useful if you have linked to a Web page). | F5
Switch between the Navigation pane and the Topic pane. | F6
Scroll through a topic. | UP ARROW and DOWN ARROW, or PAGE UP and PAGE DOWN
Scroll through all the links in a topic or through all the options on a Navigation pane tab. | TAB

For the Contents tab:

To | Press
Open and close a book or folder. | PLUS SIGN and MINUS SIGN, or LEFT ARROW and RIGHT ARROW
Select a topic. | DOWN ARROW and UP ARROW
Display the selected topic. | ENTER

For the Search tab:

To | Press
Display the Search tab. | ALT+S
Type a keyword to search for. | ALT+W, and then type the word
Start a search. | ALT+L
Select a topic in the results list. | ALT+T, and then UP ARROW and DOWN ARROW
Display the selected topic. | ALT+D
Search for a keyword in the result list of a prior search. | ALT+U
Search for words similar to the keyword. For example, to find words like "running" and "runs" for the keyword "run." | ALT+M
Search only through topic titles. | ALT+R

For the Favorites tab:

To | Press
Display the Favorites tab. | ALT+I
Add the currently displayed topic to the Favorites list. | ALT+A
Select a topic in the Favorites list. | ALT+P, and then UP ARROW and DOWN ARROW
Display the selected topic. | ALT+D
Remove the selected topic from the list. | ALT+R

There are also shortcut menu commands that can be accessed through the keyboard.

Shortcut keys also work in secondary and pop-up windows.

Every time you use a shortcut key in the Navigation pane, you lose focus in the Topic pane. To return to the Topic pane, press F6.

The Match similar words check box, on the Search tab, will be selected if you used it for your last search.


Use the Shortcut Menu Commands

There are several commands on the shortcut menu that you can use to display and customize information.

Command | Description
Right-click in the table of contents, and then click Open All. | Opens all books or folders in the table of contents. This command works only if the Contents tab is displayed.
Right-click in the table of contents, and then click Close All. | Closes all books or folders. This command works only if the Contents tab is displayed.
Right-click, and then click Print. | Prints the topic.
These commands can be accessed through the keyboard. You can click SHIFT+F10 to display the shortcut menu, and then click the appropriate shortcut keys. Or, you can enable Mousekeys. Use a Mousekey combination to display the shortcut menu, and then click the appropriate shortcut keys.


To Turn On MouseKeys

  1. Open the Accessibility Properties dialog box by clicking Start, pointing to Settings, clicking Control Panel, and then double-clicking Accessibility Options.
  2. Click the Mouse tab.
  3. Select the Use MouseKeys check box.


Customize the Help Viewer

There are a few ways to easily change the size and position of the eBook and the panes in the eBook:

  • To resize the Navigation or Topic pane, point to the divider between the two panes. When the pointer changes to a double-headed arrow, drag the divider right or left.
  • To proportionately shrink or enlarge the entire eBook, point to any corner of the eBook. When the pointer changes to a double-headed arrow, drag the corner.
  • To change the height or width of the eBook, point to the top, bottom, left, or right edge of the eBook. When the pointer changes to a double-headed arrow, drag the edge.
  • To reposition the eBook on your screen, click the title bar and drag the eBook to a new position.
When the eBook is opened again, it will appear with the last size and position settings you specified.


Print the Current Topic

Right-click a topic, and then click Print.

If you print from the Contents tab (by right-clicking an entry, and then clicking Print) you will see options to print only the current topic, or the current topic and all subtopics.


Full-Text Search

The Search tab allows you to search through every word in the Library to find a match. For example, if you do a full-text search on the word "database," every topic that contains the word "database" will be listed.

The Search tab in this Library also contains advanced full-text search functionality that allows you to search using boolean, wildcard, and nested expressions.


Use Full-Text Search

  1. Click the Search tab, and then type the word or phrase you want to find.
  2. Click List Topics, select the topic you want, and then click Display.


Advanced Full-Text Search

Advanced full-text search allows you to search using boolean, wildcard, and nested expressions. You can also limit the search to previous results, match similar words, or search topic titles only.


Search for Information

A basic search consists of the word or phrase you want to find. You can use wildcard expressions, nested expressions, boolean operators, similar word matches, a previous results list, or topic titles to further define your search.

The basic rules for formulating queries are as follows:

  • Searches are not case-sensitive, so you can type your search in uppercase or lowercase characters.
  • You may search for any combination of letters (a-z) and numbers (0-9).
  • Punctuation marks such as the period, colon, semicolon, comma, and hyphen are ignored during a search.
  • Group the elements of your search using double quotes or parentheses to set apart each element. You cannot search for quotation marks.
If you are searching for a file name with an extension, you should group the entire string in double quotes, ("filename.ext"). Otherwise, the period will break the file name into two separate terms. The default operation between terms is AND, so you will create the logical equivalent to "filename AND ext."


Find Information with Advanced Full-Text Search

  1. Click the Search tab, and then type the word or phrase you want to find.
  2. Click [image] to add boolean operators to your search.
  3. Click List Topics, select the topic you want, and then click Display.
  4. To sort the topic list, click the Title, Location, or Rank column heading.

You can precisely define a search by using wildcard expressions, nested expressions, and boolean operators.

You can request similar word matches, search only the topic titles, or search the results of a previous search.


Search for Words or Phrases

You can search for words or phrases and use wildcard expressions. Wildcard expressions allow you to search for one or more characters using a question mark or asterisk. The table below describes the results of these different kinds of searches.

Search for Example Results

A single word

select

Topics that contain the word "select." (You will also find its grammatical variations, such as "selector" and "selection.")

A phrase

"new operator"

or

new operator

Topics that contain the literal phrase "new operator" and all its grammatical variations.

Without the quotation marks, the query is equivalent to specifying "new AND operator," which will find topics containing both of the individual words, instead of the phrase.

Wildcard expressions

esc*

or

80?86

Topics that contain the terms "ESC," "escape," "escalation," and so on. The asterisk cannot be the only character in the term.

Topics that contain the terms "80186," "80286," "80386," and so on. The question mark cannot be the only character in the term.

Select the Match similar words check box to include minor grammatical variations for the phrase you search.


Define Search Terms

The AND, OR, NOT, and NEAR operators enable you to precisely define your search by creating a relationship between search terms. The following table shows how you can use each of these operators. If no operator is specified, AND is used. For example, the query "spacing border printing" is equivalent to "spacing AND border AND printing."

Search for | Example | Results
Both terms in the same topic. | dib AND palette | Topics containing both the words "dib" and "palette."
Either term in a topic. | raster OR vector | Topics containing either the word "raster" or the word "vector" or both.
The first term without the second term. | ole NOT dde | Topics containing the word "OLE," but not the word "DDE."
Both terms in the same topic, close together. | user NEAR kernel | Topics containing the word "user" within eight words of the word "kernel."
The |, &, and ! characters don't work as boolean operators (you must use OR, AND, and NOT).


Use Nested Expressions

Nested expressions allow you to create complex searches for information. For example, "control AND ((active OR dde) NEAR window)" finds topics containing the word "control" along with the words "active" and "window" close together, or containing "control" along with the words "dde" and "window" close together.

The basic rules for searching help topics using nested expressions are as follows:

  • You can use parentheses to nest expressions within a query. The expressions in parentheses are evaluated before the rest of the query.
  • If a query does not contain a nested expression, it is evaluated from left to right. For example: "Control NOT active OR dde" finds topics containing the word "control" without the word "active," or topics containing the word "dde." On the other hand, "control NOT (active OR dde)" finds topics containing the word "control" without either of the words "active" or "dde."
  • You cannot nest expressions more than five levels deep.


Search for Words in the Titles of Topic Files

  1. Click the Search tab, type the word or phrase you want to find, and then select the Search titles only check box.
  2. Click List Topics, select the topic you want, and then click Display.
If you use this option, all topic files will be searched, including any that are not listed in the table of contents.


Find Words Similar to Your Search Term

This feature enables you to include minor grammatical variations for the phrase you search. For example, a search on the word "add" will find "add," "adds," and "added."

  1. Click the Search tab, type the word or phrase you want to find, and then select the Match similar words check box.
  2. Click List Topics, select the topic you want, and then click Display.
This feature only locates variations of the word with common suffixes. For example, a search on the word "add" will find "added," but it will not find "additive."


Search Previous Results

This feature enables you to narrow a search that results in too many topics found. You can search through your results list from previous search by using this option.

  1. On the Search tab, select the Search previous results check box.
  2. Click List Topics, select the topic you want, and then click Display.
The Search tab will open with this check box selected if you previously used this feature.


About Microsoft Press

Microsoft Press is a division of Microsoft Corporation and the leading source of comprehensive self-paced learning, training, evaluation, and support resources to help everyone from developers to IT professionals to end users get the most from Microsoft® technology. Choose from more than 250 current titles in print, multimedia, and network-ready formats-learning solutions made by Microsoft, with the most timely and accurate information available. For more information, visit http://mspress.microsoft.com/.


Introduction

45 years. That's about how long computers, as we know them, have been around. When you consider the Great Pyramids of Egypt have been around for over 3000 years, 45 years feels, and probably is, insignificant. Yet no other technology has so profoundly changed the lives of so many in such a short time. When you consider the explosion of the Internet, computers have more than likely irrevocably changed what the future will be like.

You can't move around today without running into a computer in some form or another—in most grocery and department stores, gas stations, and so forth. How did we get to this point? What has happened within the world of computers that has caused them to be so widely implemented? How is it that computers have been so quickly deployed, literally right under our nose, without us really knowing it?


Computing Advances and Networking

Computing advances are the result of two major factors: the drive to make computers smaller, faster, and less expensive, and more recently, the explosion of the Internet. These two items alone have

  • Provided a means to capture the entire abilities of a computer within a very small device
  • Allowed us to network these computers and devices together, not only within a given wired network, but also wirelessly and across the world.

There appears to be no boundaries today, but the hardware and network only represent part of the equation. The other part is the content, or data itself, that is transmitted, exchanged, and passed between machines. Enter XML!


XML for Data Description

EXtensible Markup Language (XML) has taken hold and taken off over the last few years. The ability to use and leverage XML to its fullest potential is no longer a dream, but is becoming a reality. With the most recent releases of server and client software products from popular suppliers, using XML in the real world is not only possible, but is often the preferred method for exchanging data and interconnecting applications.

While XML does present challenges of its own, the ease of integration and the flexibility present within a good XML architecture is hard to pass up when creating innovative solutions. These are solutions that were only dreamed about before, or at the very least costs millions of dollars to implement—ask the Electronic Data Interchange (EDI) folks about that. XML, on the other hand, provides an inexpensive means to markup and describe your data, and with the wealth of tools and applications which support it today, you can bet on easy integration.

While we could ramble on about how great XML is, we are well aware that developers want to skip the fluff and get right into the gory details. For that reason, we're going to quickly roadmap where you can expect to go within the pages of this book, and then turn you loose.


About the Book

This book is broken into five parts. Part I, "Getting Started", accomplishes a few educational and informative tasks. Chapter 1 provides information about the challenges historically associated with building client/server applications and solutions. It then introduces you to XML and how it is here to save the day—all of this before touching on how Microsoft, with its .NET initiative, has created tools and servers to support the next generation of server-to-server communications and collaborations.

Chapters 2 and 3 take you through XML basics and the details of XML parsers. You can expect to briefly cover everything from language semantics and syntax to the differences between DOM (Document Object Model) and SAX (Simple API for XML) parsing of XML documents.

Part II, "Application Development", is where you more than get your feet wet creating real-world XML solutions and .NET applications. Chapters 4-7 will bob and weave through creating database, Web, user interface, and messaging applications while exposing you to standards like SOAP (Simple Object Access Protocol) and XSLT (eXtensible Stylesheet Language Transformations). You will then dive into the world of metadata in Chapter 8, and Chapters 9 and 10 will walk you through building both a server and client application based on .NET and its supporting technologies. XML, of course is at the heart of these chapters.

Part III, "Interoperability", gives you a breather from coding, but don't expect much of one. Chapters 11-13 will discuss and cover topics on creating multi platform solutions, integrating with legacy systems, and building solutions that can be accessed, and therefore easily deployed, on devices like handhelds and cell phones.

We will focus on "XML and Microsoft .NET" in Part IV. While we will have touched on items like SOAP and BizTalk in previous chapters, Chapters 14 and 15 we cover them in detail. Chapter 16, will pull together all the information you have learned about XML and .NET to discuss how .NET works and what can be achieved with it (beyond what our examples have demonstrated).

Finally we have Part V, the Appendices. While most appendices in books offer rather useless "resource" information, Appendix A and Appendix B are full blown chapters. In designing the structure of the book we felt it was important to not only discuss some of the related standards, like XSD (XML) Schema, but also the tools that are available to work with these XML-based efforts. We decided to include these as appendices for your reading enjoyment. Don't pass these up, because you will find some useful information and examples here.


About the CD

The source code for the programs discussed in this book is included on the companion CD and is arranged by chapter. Use the Setup program included on the CD to copy the samples onto your hard disk. They require about 3 MB of hard disk space. If your computer's operating system is Microsoft Windows NT 4.0, Microsoft Windows 2000 or Microsoft Windows XP, you will need administrative privileges to install the sample files. To uninstall the sample files, make the appropriate selection from Add/Remove Programs in the Control Panel. Many examples require the Microsoft XML Core Services 4.0 Parser (also included on the CD) to be installed on your computer. For more information, refer to the Readme.txt file in the root folder of the companion CD.

The examples in chapters 5, 6, and 10 require Internet Information Server (IIS) or Microsoft Personal Web Server to be running on your computer. The folders containing these examples ("ch05 code", "ch06 code", and "ch10 code") should be shared on your web server. To do so, right-click the folder in Windows Explorer and click Sharing in the context menu. Click the Web Sharing tab and specify the share name.

In order to run the examples in chapters 8, 9, 10, 13, 14, and 16 you will need the .NET Framework. These examples can also be run under Visual Studio .NET, although this software is NOT required to run the examples.


Conventions

Once you dive into the book and code examples, you will come across instances where lines of code are longer than the width of a single line on the page. In these instances we've wrapped the code back to the first position on the page, which makes it obvious (especially when the line that wraps is a nested line of code) that this code is from the previous line. As there are no end-of-line markers in XML, we've tried to break long lines of code in such a way that you can easily see what's going on. Hyphens at the end of lines of code that wrap are part of the actual code (or are mistakes and should be ignored).

If you are ever in doubt about code structure, refer to the source code on the CD that contains the proper structure.


Diving In

That concludes our introduction. We have tried to keep it short and sweet. We have included some interesting and detailed examples within the book, and we have covered a ton of topics, standards, and overall theories about the deployment of XML-based solutions. We hope you enjoy the book!

- R. Allen Wyke

- Brad Leupen

- Sultan Rehman


44ap02a.htm

Appendix B